#!C:\strawberry\perl\bin\perl
#------------------------------------------------------------------------------------------
# LanMngmtXL
# Purpose: document the network
# Copyright 2012-2014 Miguel Rosa
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#------------------------------------------------------------------------------------------
use strict;
use warnings; no if $] >= 5.018, warnings => "experimental"; #no if $] >= 5.018, warnings => "experimental::smartmatch"; no if $] >= 5.018, warnings => "experimental::lexical_subs";
use vars;
use Data::Dumper::Concise;																									# For debug output
use Getopt::Long;																											# For parsing argument options
use Time::HiRes qw( time );																									# For measuring time processing
use POSIX qw[ _exit ];																										# For faster exit
use File::Basename;																											# For filename processing
BEGIN { push @INC,dirname(__FILE__) }																						# Include script directory to find self modules
require ServicePort;																										# Module with service port name translations

# Variable declaration (check also "#Reset vars" section)
our ($DEBUG,$EXCEL,$MYSQL,$Ignored,$ExpandGroups,$TranslateNames,$XLcode,$NotFound,$NetList,$LayPhysical,$LayLogical,$LayNetwork,$LayFlow,$Covered)=(1,1,0,0,1,1,1,1,1,1,1,1,1,0); 	#Features
our ($wvt_dir,$plink)=('C:\\WVT\\PROGRAM','C:\\Putty\\plink.exe');															# Location of Checkpoint WVT and plink (from PuTTY) tools
our ($count,$flag_more,$flag_obj,$dl,$r,$v,$timeout,$DRYRUN,$expand,$nacl)=(1,0,0,0,0,0,4,0,0,0);
our ($start,$now,$before)=(time(),0,0);																						# start script execution timer
our ($host,$name,$action,$cell,$desc,$ext,$file,$if,$if2,$ifs,$ip,$src,$dst,$src_t,$dst_t,$map,$member,$node,$row,$rule,$prot,$srvc,$srvc2,$srvc_t,$status,$sub,$sw,$vpn,$xml,$x,$p,$z)=
	('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
our ($src_m,$dst_m,$prot_m,$srvc_m,$srvc2_m,$src_t_m,$dst_t_m,$srvc_t_m,$src_mm,$dst_mm,$prot_mm,$srvc_mm,$srvc2_mm,$src_t_mm,$dst_t_mm,$srvc_t_mm,$more,$flag,$return,$c,$d,$n,$n1,$n2,$n3,$n4,$n5,$n6,$n7,$n8)=
	('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
our ($t,$x1,$x1b,$x2,$x2b,$x3,$x3b,$x4,$x4b,$l1,$l1b,$l2,$l2b,$p1,$e,$u,$y1,$y1b,$y2,$y2b,$y3,$y3b,$y4,$y4b,$m1,$m1b,$m2,$m2b,$q1,$f,$fl,$b,$w,$w1,$w2)=
	('',-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,'','','',-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,'','','00',0,1,'','');
our (@configs,@dir,@buffer,@src,@dst,@srvc,@objects,@objhost,@objunk,@xtend,@used,@match,@layphy_e,@laylog_e,@laynet_e,@layflow_e,@a,@b)=((),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),());
our  ($obj,$obj2,$acl,$nat,$xlate,$members,$members_aux,$members_used,$hash,$th,$domain,$user,$policy,$auxt,$aux,$mac,$switch,$net,$layphy_v,$laylog_v,$laynet_v,$layflow_v)=({},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{});
our ($dir,$opt_u,$opt_p,$opt_e,$opt_f,$addr,$type,$session,$cmd,$ua,$req,$out)=("cfgdir",'','','','','','','','','','','');
our @CMD=();
our @CMDsw=("sh running","sh ip int br","sh int status","sh ip route","sh ip arp","sh vlan br","sh int trunk","sh mac addr","sh span det","sh standby br","sh hsrp br","sh vrrp br","sh glbp br","sh vpc br","sh storm","sh int count storm","exit");
our @CMDrt=("sh running","sh ip int br","sh ip route","sh ip arp","sh standby br","sh vrrp br","sh glbp br","exit");
our @CMDsplat=("route","ifconfig","arp -a","exit");
our ($CMDfw,$CMDasa_f,$CMDasa,$CMDipso)=('','admin/exec/sh%20int%20ip%20brief/sh%20arp','admin/exec/sh%20running/sh%20route/sh%20int%20ip%20brief/sh%20arp','echo show route | clish; echo show vrrp interfaces | clish; ifconfig -a; arp -a; exit');
our ($CMDhost,$CMDbigip)=('route; ifconfig; arp -a; exit','bigpipe export; route; ifconfig; arp -a; exit');
our ($ExcelFile,$SearchFile,$workbook,$xls,$header_fmt,$global_fmt)=('LanMngmtXL.xlsx','LanMngmtXL-search.xlsx','','','','');
our $h={'origin'=>0,'name' =>1,'class'=>2,'member'=>3,'vlan' =>4,'L1'     =>5,'L2'        =>6,'L3'   =>7,'L4'     =>8,'acl'   =>9,'vpn'=>10,'action'=>11,'status'=>12,'desc'=>13,'XLcode'=>14,
		'node1' =>0,'node2'=>1,'color'=>2,'width' =>3,'style'=>4,'opacity'=>5,'visibility'=>6,'label'=>7,'l_color'=>8,'l_size'=>9};
our $i={0=>'origin',1=>'name',2=>'class',3=>'member',4=>'vlan',5=>'L1',6=>'L2',7=>'L3',8=>'L4',9=>'acl',10=>'vpn',11=>'action',12=>'status',13=>'desc',14=>'XLcode' };
our @col    =('origin','name','class','member','vlan','L1','L2','L3','L4','acl','vpn','action','status','desc','XLcode');
our @maincol=('origin','name','class','member','vlan','L1','L2','L3','L4','acl','vpn','action','status','desc');
our @auxcol=('key','value');
our @laycol_e=('node1','node2','color','width','style','opacity','visibility','label','l_color','l_size');
our @laycol_v=('node','color','shape','size','opacity','i_file','visibility','label','l_color','l_position','tooltip');
our $header_col; %{$header_col}=('X1-Main'	=>\@maincol,	'X2-Xtend'	=>\@col,		'X3-Aux'	=>\@auxcol,		'X4-Net'	=>\@auxcol,		'X5-Phy_e'	=>\@laycol_e,	'X5-Phy_v'	=>\@laycol_v,
								 'X6-Log_e'	=>\@laycol_e,	'X6-Log_v'	=>\@laycol_v,	'X7-Net_e'	=>\@laycol_e,	'X7-Net_v'	=>\@laycol_v,	'X8-Flow_e'	=>\@laycol_e,	'X8-Flow_v'	=>\@laycol_v);
our $tb;		 %{$tb}		   =('X1-Main'	=>\@objects,	'X2-Xtend'	=>\@xtend,		'X3-Aux'	=>$auxt,		'X4-Net'	=>$net,			'X5-Phy_e'	=>\@layphy_e,	'X5-Phy_v'	=>$layphy_v,
								 'X6-Log_e'	=>\@laylog_e,	'X6-Log_v'	=>$laylog_v,	'X7-Net_e'	=>\@laynet_e,	'X7-Net_v'	=>$laynet_v,	'X8-Flow_e'	=>\@layflow_e,	'X8-Flow_v'	=>$layflow_v);
our ($dbhost,$dbuser,$dbpass,$db,$dbh,$sth,$ref)=('localhost','root','mysql','lan-mngmt','','','');							# MySQL database connection and authentication values

# Start function: dbg
DBG->autoflush;
sub dbg{																													# Prints debug information
	if($DEBUG){ print DBG @_ }
}
# End function: dbg

# Start function: memory_usage
use Win32::OLE qw/in/;																										# Crashes Perl when used together with Parallel::...
sub memory_usage{
    my $objWMI = Win32::OLE->GetObject('winmgmts:\\\\.\\root\\cimv2');
    my $processes = $objWMI->ExecQuery("select * from Win32_Process where ProcessId=$$");
    foreach my $proc (in($processes)) { return $proc->{WorkingSetSize}; }
#	return 0;				# disabled previous lines when using Parallel::... modules
}
# End function: memory_usage

# Start Function get
sub get{																													# Retrieves information from network devices and saves intos files
	use Socket;
	use IO::Socket::Socks::Wrapper { ProxyAddr=>'127.0.0.1', ProxyPort=>1080, SocksDebug=>0, Timeout=>4 };
	use IO::Socket::SSL qw();
	use Net::Telnet::Cisco;																									# To telnet to Cisco devices
	use LWP;																												# For HTTPS access to devices
	use LWP::Protocol::socks;																								# For HTTPS access with SOCKS
	use HTML::Entities;																										# For Excel file reading process
	use Text::ParseWords;
	use IO::Handle;
	use File::Copy;
	GetOptions ("f=s"=>\$opt_f,"u=s"=>\$opt_u,"p=s"=>\$opt_p,"o=s"=>\$dir,"d"=>\$DRYRUN); 									# Get options: -u user -p pass -f device-list.txt [-d] [-o outputdir]
	unless(-d $dir){ mkdir($dir) or die "could not create dir: $!" }														# Create directory to save output files
	open FILE,"< $opt_f" or die "Can't open list file (-f option) $opt_f : $!";												# Open file with device list
	while ($z=<FILE>){
		chomp $z;
		if (($z !~ m/^\s*#.*$/) and ($z=~m/^([^;]+);([^;]+);([^;]+);?([^;]+)?.*$/)){										# Ignore comment lines, get address, name and type of device
			($addr,$name,$type,$n)=($1,$2,$3,$4); if(!$n){ $n='' };
			if($4){ print "\nAddr: $1, Name: $2, Type: $3, Method: $4\n"; &dbg("Addr: $1, Name: $2, Type: $3, Method: $4\n") }
			else{ print "\nAddr: $1, Name: $2, Type: $3\n"; &dbg("Addr: $1, Name: $2, Type: $3\n") };
			if($type=~/switch/||$type=~/router/){
				if($type=~m/switch/){ @CMD=@CMDsw }elsif($type=~m/router/){ @CMD=@CMDrt };
				eval {																										# Try SSHv2
					&dbg("Trying SSHv2: $plink -ssh -2 -4 -batch $opt_u\@$addr -pw <pass> < plink-cmd.txt\n"); print "SSHv2 ";
					`ECHO OFF > $dir\\$name\+$addr\.cisco`; open(CMD,">plink-cmd.txt") or die "Unable to create file\n"; 
					print CMD "enable\nterm len 0\n"; foreach $cmd (@CMD){ print CMD "$cmd\n" }; close CMD;
					`$plink -ssh -2 -4 -batch $opt_u\@$addr -pw $opt_p < plink-cmd.txt >> $dir\\$name\+$addr\.cisco`;
				} unless $DRYRUN||($n=~m/(telnet|sshv1)/i);
				if ($@||$?||($n=~m/(telnet|sshv1)/i)){																		# Try SSHv1
					eval {
						&dbg("Trying SSHv1: $plink -ssh -1 -4 -batch $opt_u\@$addr -pw <pass> < plink-cmd.txt\n"); print "SSHv1 ";
						`ECHO OFF > $dir\\$name\+$addr\.cisco`; open(CMD,">plink-cmd.txt") or die "Unable to create file\n"; 
						print CMD "enable\nterm len 0\n"; foreach $cmd (@CMD){ print CMD "$cmd\n" }; close CMD;
						`$plink -ssh -1 -4 -batch $opt_u\@$addr -pw $opt_p < plink-cmd.txt >> $dir\\$name\+$addr\.cisco`;
					} unless $DRYRUN||($n eq 'telnet');																		# Try Telnet
						if ($@||$?||($n eq 'telnet')){
						eval {
							&dbg("Trying Telnet: $addr with user $opt_u\n"); print "Telnet ";
							$session=Net::Telnet::Cisco->new(Host=>"$addr",Input_log=>"$dir\\$name\+$addr\.cisco",Timeout=>$timeout); $session->login($opt_u,$opt_p);
							$session->cmd("enable"); if($type=~m/-e/){ $session->enable("$opt_p") }; $session->cmd("term len 0"); 
							foreach $cmd (@CMD){ eval { $session->cmd("$cmd") }; if ($@){ &dbg("Error:$type:$cmd: $@\n") } }
							$session->close;
						} unless $DRYRUN;
			}	}	}
			elsif($type=~/asa/){
				if($type eq "asa-f"){ $CMDfw=$CMDasa_f }else{ $CMDfw=$CMDasa };
				&dbg("Trying HTTPS: https://$addr/$CMDfw\n");
				eval {																										# Use HTTPS for ASA devices (telnet/ssh don't work well)
					$ua=LWP::UserAgent->new(ssl_opts=>{verify_hostname=>0,SSL_verify_mode=>0x00}); $ua->timeout($timeout); $ua->proxy([qw/ http https /] => "socks://127.0.0.1:1080");
					$req=HTTP::Request->new(GET=>"https://$addr/".$CMDfw); $req->authorization_basic("$opt_u", "$opt_p");
					$out=$ua->request($req)->as_string; open(OUT, ">$dir\\$name\+$addr\.cisco") or warn "Can't write: $!"; print OUT $out; close(OUT)
				} unless $DRYRUN;
				if ($@){ &dbg("Error:https://$addr/$CMDfw: $@\n") }
			}
			elsif($type=~/ipso/){
				&dbg("Trying SSH: $plink -ssh -2 -4 -batch $opt_u\@$addr -pw <pass> $CMDipso\n");			# use SSH for IPSO devices
				eval { `$plink -ssh -2 -4 -batch $opt_u\@$addr -pw $opt_p "$CMDipso" >$dir\\$name\+$addr\.ipso` } unless $DRYRUN; if ($@){ &dbg("Error:$type:$cmd: $@\n") }
			}
			elsif($type=~/splat/){
				&dbg("Trying SSH: $plink -ssh -2 -4 -batch $opt_u\@$addr -pw <pass> < plink-cmd.txt\n");
				eval {																										# use SSH for SPLAT devices
					`ECHO OFF > $dir\\$name\+$addr\.splat`; open(CMD,">plink-cmd.txt") or die "Unable to create file\n"; foreach $cmd (@CMDsplat){ print CMD "$cmd\n" }; close CMD;
					`$plink -ssh -2 -4 -batch $opt_u\@$addr -pw $opt_p < plink-cmd.txt >> $dir\\$name\+$addr\.splat`;
				} unless $DRYRUN;
			}
			elsif($type=~/bigip/){
				&dbg("Trying SSH: $plink -ssh -2 -4 -batch $opt_u\@$addr -pw <pass> $CMDbigip\n");
				eval { `$plink -ssh -2 -4 -batch $opt_u\@$addr -pw $opt_p "$CMDbigip" >$dir\\$name\+$addr\.bigip` } unless $DRYRUN;	# use SSH for BigIP devices
				if ($@){ &dbg("Error:$type:$cmd: $@\n") }
			}
			elsif($type=~/alteon/){
				&dbg("Trying Telnet: $addr with user $opt_u\n");
				eval {
					$session=Net::Telnet::Cisco->new(Host=>"$addr",Input_log=>"$dir\\$name\+$addr\.alteon",Timeout=>$timeout,Prompt=>'/>>.*#/'); $session->login($opt_u,$opt_p);
					$session->cmd("/cfg/dump");$session->cmd(String=>"/info/dump",Prompt=>'/Confirm dumping all information.*/');$session->cmd(String=>"y",Prompt=>'/>>.*#/');
					$session->cmd("exit");$session->close;
				} unless $DRYRUN;
			}
			elsif($type=~/host/){
				&dbg("Trying SSH: $plink -ssh -2 -4 -batch $opt_u\@$addr -pw <pass> $CMDhost\n");
				eval { `$plink -ssh -2 -4 -batch $opt_u\@$addr -pw $opt_p "$CMDhost" >$dir\\$name\+$addr\.host` } unless $DRYRUN; if ($@){ &dbg("Error:$type:$cmd: $@\n") }
			}
			elsif($type=~/wvt/){
				&dbg("Trying WVT: $wvt_dir\\cpdb2web -s $addr -u $opt_u -p <pass> -o $wvt_dir\\xsl\\XML\\ \n");
				eval {																										# use WVT tool for Checkpoint SCS
					mkdir "$wvt_dir\\xsl\\XML-backup";
					foreach $file (glob "$wvt_dir\\xsl\\XML\\*"){ move($file,"$wvt_dir\\xsl\\XML-backup\\"); &dbg("Backup: $file\n") };	# perform a backup first
					`$wvt_dir\\cpdb2web -s $addr -u $opt_u -p $opt_p -o $wvt_dir\\xsl\\XML\\`;								# generate XML files
					opendir (DIRHANDLE, "$wvt_dir\\xsl\\XML\\") or die "could not open dir: $!";							# move files to output directory
					foreach $file (readdir(DIRHANDLE)) {
						next unless -f "$wvt_dir\\xsl\\XML\\$file";	move ("$wvt_dir\\xsl\\XML\\$file", "$dir\\$name\+$file") or warn "could not rename file";
						&dbg("WVT: $dir\\$name\+$file\n")
					}
				} unless $DRYRUN;
	}	}	};
	close FILE;	if($DRYRUN){ print "Check LanMngmtXL-debug.txt for detail\n" }
}
# End Function get

# Start function: class
sub class{																													# Returns default IP class of address
	my ($ip)=@_; 																											# Input: IP address
	$p=32; if($ip=~m/^(\d{1,3})\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){ if($1<127){ $p=8 }elsif($1>127 && $1<192){ $p=16 }elsif($1>191 && $1<224){ $p=24 } }; 
	return $p;																												# Output: Class prefix
}
# End function: class

# Start function: copy_obj
sub copy_obj{																												# Creates a copy of an object
	my ($orig)=@_; 																											# Input: object
	my $copy={}; foreach(@col){ if(exists($$orig{$_})){ $copy->{$_}=$orig->{$_} } }; 
	return $copy;																											# Output: copy of object
}
# End function: copy_obj

# Start function: get_members
sub get_members{																											# Returns all members of a group (in recursive action)
	my ($name,$host,$map,$n)=@_;				 																			# Input: object group name, host name, object type, members list 
	my ($return,$x,$n1,$n2)=('','','',''); 
	#&dbg("get_members:$name,$host,$map,$n\n");
	if($name=~m/^m:$host:$map:$n\s*$/){ $return="$x " }
	else{ 
		foreach $x (split(/\s+/, $n)){
			$members_used->{lc($name)} .= lc("$host:$map:$x ");																	# For object use
			if(exists($$members_aux{"m:$host:$map:$x"})){ $return .= $members_aux->{"m:$host:$map:$x"} }						# Avoid expand already previously expanded members
			else{
				if($x=~m/^(\S+)~(\S+)$/){																						# Case of "group with exceptions"
					$n1=$1; $n2=$2; $members_used->{lc($name)} .= lc("$host:$map:$n1 ");	$members_used->{lc($name)} .= lc("$host:$map:$n2 ");		# For object use
					if((exists($$members{"m:$host:$map:$n1"}))&&($members->{"m:$host:$map:$n1"} ne "$n1 ")){ 
						if(exists($$members_aux{"m:$host:$map:$n1"})){ $p=$members_aux->{"m:$host:$map:$n1"} }else{ $p=&get_members($name,$host,$map,$members->{"m:$host:$map:$n1"}) } }
					else{ $p="$n1 " };
					if((exists($$members{"m:$host:$map:$n2"}))&&($members->{"m:$host:$map:$n2"} ne "$n2 ")){ 
						if(exists($$members_aux{"m:$host:$map:$n2"})){ $z=$members_aux->{"m:$host:$map:$n2"} }else{ $z=&get_members($name,$host,$map,$members->{"m:$host:$map:$n2"}) } }
					else{ $z="$n2 " };
					foreach $c (split(/\s+/, $p)){ foreach $n (split(/\s+/, $z)){ $return .= $c.'~'.$n.' '; } } }
				elsif((exists($$members{"m:$host:$map:$x"}))&&($members->{"m:$host:$map:$x"} ne "$x ")){ 						# normal group case
					$return .= &get_members($name,$host,$map,$members->{"m:$host:$map:$x"}) }
				else{ $return .= "$x " } 
	}	}	}
	#&dbg("get_members:$name,$host,$map,$n Returns:$return\n");
	return $return																											# Output: members list expanded
}
# End function: get_members

# Start function: network
sub network{																												# Return network address given IP and prefix
	my ($ip)=@_; 																											# Input: IP (w/prefix)
	my $prefix=0; my @aux=(); my $return=""; 
	if($ip=~m/([\d\.]+)\/(\d+)/){ $ip=$1; $prefix=$2 }else{ $prefix=32 }; $return="\/$prefix";
	@aux=split(/\./,$ip); ($ip)=unpack("N",pack("C4",@aux)); $prefix=oct("0b".('1' x $prefix).('0' x (32-$prefix))); @aux=unpack("C4",pack("N",($ip&$prefix)));
    return join(".",@aux).$return;																							# Output: network w/prefix
}
# End function: network

# Start function: xlcode
use Net::IP;																												# Process IP addresses
my ($first,$last)=(new Net::IP('127.0.0.1',4),new Net::IP('127.0.0.1',4));
sub xlcode{																													# Returns XLcode of L3 or L4 types
	my ($z,$n2)=@_; my ($n,$m,$return,$plus)=('','','',''); if(!$n2){ $n2='LX'}; 
	if(($n2 eq 'LX')||($n2 eq 'L3')){
		foreach $n (split(/,/,$z)){
			if($n=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d+)?(\-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d+)?$/){ $n=$1.$3 }		#Format correction
			if($n=~m/^0\.0\.0\.0(\/0)*$/){ $n=''; $n2='' }																	#Case "any" L3
			elsif($n=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/32)?$/){													#Case L3 (single IP - same as below but faster)
				$n =''; foreach $n1 (split(/\./,$1, 4)){ $n .= sprintf("%02x",$n1) }; $n .= '-'; foreach $n2 (split(/\./,$1, 4)){ $n .= sprintf("%02x",$n2) }; $n2='' }
			elsif(($n=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d+)$/)||($n=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)){			# Case L3 (ip/network/range)
				$first->set($n,4)||$first->set($1,4); $last->set($first->last_ip(),4);
				$n =''; foreach $n1 (split(/\./,$first->ip, 4)){ $n .= sprintf("%02x",$n1) }; $n .= '-'; foreach $n2 (split(/\./,$last->ip,  4)){ $n .= sprintf("%02x",$n2) }; $n2='' }
			else{ $n='' }
			$return .= $n.'|';
		}	
		if($return=~m/^\|*$/){ $return='' }
	}	
	if(($n2 eq 'LX')||($n2 eq 'L4')){
		foreach $n (split(/,/,$z)){
			if($n=~m/^#0$/){ $n=''; $m='' }																					# Case "any" or "not equal" L4
			elsif($n=~m/^.*#(\d+)&src:(.*)$/){ $n=sprintf("%04x",$1).'-'.sprintf("%04x",$1);  $m=&xlcode($2,'L4') }
			elsif($n=~m/^.*#(\d+)-(\d+).*$/){ $n=sprintf("%04x",$1).'-'.sprintf("%04x",$2); $m='' }							# Case L4 (port)
			elsif($n=~m/^.*gt#(\d+).*$/){ $n=sprintf("%04x",$1).'-FFFF'; $m='' }
			elsif($n=~m/^.*lt#(\d+).*$/){ $n='0000-'.sprintf("%04x",$1); $m='' }
			elsif($n=~m/^.*(eq|neq)?#(\d+).*$/){ $n=sprintf("%04x",$2).'-'.sprintf("%04x",$2); $m='' }
			else{ $n='' }
			$return .= $n.'|'; $plus .= $m.'|';
		}
		if($return=~m/^\|*$/){ $return='' }; if($plus=~m/^\|*$/){ $plus='' }; 
	}
	$return=~s/\|$//; if($plus){ $return = lc($return).'M'.lc($plus) }else{ $return=lc($return) }; $return=~s/\|$//; 
	return $return;
}
# End function: xlcode

# Start function: uncodeXL
sub uncodeXL{																												# Convert hex IP or service to (dotted) decimal
	my ($n)=@_; my $return;
	if($n=~m/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})-([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/){
		if("$1$2$3$4" eq "$5$6$7$8"){ $return=hex($1).'.'.hex($2).'.'.hex($3).'.'.hex($4) }else{ $return=hex($1).'.'.hex($2).'.'.hex($3).'.'.hex($4).'-'.hex($5).'.'.hex($6).'.'.hex($7).'.'.hex($8) } }
	elsif($n=~m/^([0-9a-f]{4,7})-([0-9a-f]{4,7})$/){ if("$1" eq "$2"){ $return=hex($1) }else{ $return=hex($1).'-'.hex($2) } }
	else{ $return=$n }
	return $return;
}
# End function: uncodeXL

# Start function: submatch
sub xlsubmatch{
	my($fl,$a,$b)=@_; my($c,$d,$e,$all,$return)=('','','',0,1); (@a,@b)=((),());
	($x1b,$x2b,$y1b,$y2b,$l1b,$l2b,$m1b,$m2b)=('','','','','','','','');
	#&dbg("XLsubmatch start: $fl: $a vs $b\n");
	if(($a eq $b)||((($a=~m/Ntcp/)||($a=~m/Nudp/))&&($b eq 'Nip'))){ $return=1 }
	elsif($b=~m/N$/){ $return=0 }
	else{
		if($fl eq 'L4'){
			if($a=~m/^(.*)N(.*)$/){ $c=$1;$d=$2; $a=''; if(!$c){ $a=$d }else{ @a=split(/\|/,$c);@b=split(/\|/,$d); for ($e=0; $e<scalar(@a); $e++){ $a .= $a[$e].':'.$b[$e].'|' } $a=~s/\|$// } }
			if($b=~m/^(.*)N(.*)$/){ $c=$1;$d=$2; $b=''; if(!$c){ $b=$d }else{ @a=split(/\|/,$c);@b=split(/\|/,$d); for ($e=0; $e<scalar(@a); $e++){ $b .= $a[$e].':'.$b[$e].'|' } $b=~s/\|$// } }
		}
		#&dbg("XLsubmatch parse: $fl: $a vs $b\n");
		foreach $c (split(/\|/,$a)){
			if($fl eq 'L3'){ if($c=~m/([0-9a-f]{8})-([0-9a-f]{8})/){ ($x1b,$x2b)=($1,$2) } }
			elsif($fl eq 'L4'){ if($c=~m/([0-9a-f]{4,7})-([0-9a-f]{4,7})/){ ($l1b,$l2b)=($1,$2) } }
			else{ &dbg("XLsubmatch *FORMAT-ERROR*:$c\n") }
			$all=0;
			if(!$all){
				foreach $d (split(/\|/,$b)){
					if($fl eq 'L3'){ if($d=~m/([0-9a-f]{8})-([0-9a-f]{8})/){ ($y1b,$y2b)=($1,$2) } }
					elsif($fl eq 'L4'){ if($d=~m/([0-9a-f]{4,7})-([0-9a-f]{4,7})/){ ($m1b,$m2b)=($1,$2) } }
					else{ &dbg("XLsubmatch *FORMAT-ERROR*:$d\n") }
					#&dbg("XLsubmatch: matching $c / $x1b-$x2b vs $d / $y1b-$y2b\n");
					if(($c eq $d)
					 ||($x1b&&$y1b&&(hex($x1b)>=hex($y1b)&&hex($x2b)<=hex($y2b)))
					 ||($l1b&&$m1b&&(hex($l1b)>=hex($m1b)&&hex($l2b)<=hex($m2b)))){ $all=1 }
				}
			}
			if(!$all){ $return=0 }
	}	}
	#&dbg("XLsubmatch end: $return\n");
	return $return
}
# End function: submatch

# Start function: xlmatch
sub xlmatch{																												# Returns true if 1st arg equals or inside 2nd arg
	my ($n1,$n2,$n3)=@_;
	($b,$w)=(0,1);
	#&dbg(Dumper("xlmatch start: $n1 vs $n2",$n3));
	if($n1 ne $n2){ 
		if(!$n3||($n3 eq 'layout')||($n1 ne $w1)){
			($t,$x1,$x2,$x3,$x4,$l1,$l2,$p1,$e)=('','','','','','','','','');
			if($n1=~m/^(.)X([0-9a-f\|-]+)?Y?([0-9a-f\|-]+)?Z?([0-9a-f\|-]+)?K?([0-9a-f\|-]+)?XL([0-9a-f\|-]+)?M?([0-9a-f\|-]+)?N?(\S+)?L(.*)$/){
				($t,$x1,$x2,$x3,$x4,$l1,$l2,$p1,$e)=($1,$2,$3,$4,$5,$6,$7,$8,$9); 
				$t='' unless defined $t; $p1='' unless defined $p1;  $e='' unless defined $e; 
				$x1='' unless defined $x1; $x2='' unless defined $x2; $x3='' unless defined $x3; $x4='' unless defined $x4; $l1='' unless defined $l1; $l2='' unless defined $l2; 
			}
			else{ if($n3&&$n3 eq 'layout'){ $x1=$x2=$x3=$x4=$n1 }else{ $x1=$x2=$x3=$x4=$l1=$l2=$n1 } }						# Just simple match (search or layout)
		}
		if(!$n3||($n3 eq 'layout')||($n2 ne $w2)){
			($u,$y1,$y2,$y3,$y4,$m1,$m2,$q1,$f)=('','','','','','','','','');
		    if($n2=~m/^(.)X([0-9a-f\|-]+)?Y?([0-9a-f\|-]+)?Z?([0-9a-f\|-]+)?K?([0-9a-f\|-]+)?XL([0-9a-f\|-]+)?M?([0-9a-f\|-]+)?N?(\S+)?L(.*)$/){
				($u,$y1,$y2,$y3,$y4,$m1,$m2,$q1,$f)=($1,$2,$3,$4,$5,$6,$7,$8,$9); 
				$u='' unless defined $u; $q1='' unless defined $q1; $f='' unless defined $f;
				$y1='' unless defined $y1; $y2='' unless defined $y2; $y3='' unless defined $y3; $y4='' unless defined $y4; $m1='' unless defined $m1; $m2='' unless defined $m2; 
			}
			else{ if($n3&&$n3 eq 'layout'){ $y1=$y2=$y3=$y4=$n2 }else{ $y1=$y2=$y3=$y4=$m1=$m2=$n2 }}						# Just simple match (search or layout)
		}
		#&dbg("xlmatch parse: $t,$x1,$x2,$x3,$x4,$l1,$l2,$p1,$e vs $u,$y1,$y2,$y3,$y4,$m1,$m2,$q1,$f\n");
		if(($n3&&$t eq $u)||($n3&&$n3 eq 'layout'))															{ $b += 0b100000000 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match object type
		if($n3&&$w&&($e eq '')&&($f eq ''))																	{ $b += 0b000000001 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match no use of special condition
		if($w&&((!$n3&&!$y1&&!$x1)||((($n3&&!$y1)||($y1&&$x1&&xlsubmatch('L3',$x1,$y1))))))					{ $b += 0b010000000 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match L3 #1
		if($w&&((!$n3&&!$y2&&!$x2)||((($n3&&!$y2)||($y2&&$x2&&xlsubmatch('L3',$x2,$y2))))))					{ $b += 0b001000000 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match L3 #2
		if($w&&((!$n3&&!$y3&&!$x3)||((($n3&&!$y3)||($y3&&$x3&&xlsubmatch('L3',$x3,$y3))))))					{ $b += 0b000100000 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match L3 #3
		if($w&&((!$n3&&!$y4&&!$x4)||((($n3&&!$y4)||($y4&&$x4&&xlsubmatch('L3',$x4,$y4))))))					{ $b += 0b000010000 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match L3 #4
		if($w&&((!$n3&&!$m1&&!$l1)||((($n3&&!$m1)||($m1&&$l1&&xlsubmatch('L4',$l1.'N'.$p1,$m1.'N'.$q1))))))	{ $b += 0b000001000 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match L4 #1
		if($w&&((!$n3&&!$m2&&!$l2)||((($n3&&!$m2)||($m2&&$l2&&xlsubmatch('L4',$l2.'N'.$p1,$m2.'N'.$q1))))))	{ $b += 0b000000100 }elsif($n3&&$n3 eq 'quick'){ $w=0 }				# match L4 #2
		if($w&&(					((($n3&&!$q1)||($q1&&$p1&&xlsubmatch('L4','N'.$p1,'N'.$q1))))))			{ $b += 0b000000010 } 								# match L4 prot name
	}else{ $b=0b111111111 }																									# if exactly equal no need for detailed comparison
	$w1=$n1; $w2=$n2;																										# remember last comparison
	#&dbg(sprintf("xlmatch end: %09b\n",$b));
	return $b;
}
# End function: xlmatch

# Start function: unique
sub unique{																													# Reduces array with unique edges
	my @list=@_; my %seen=(); my $e={}; my @return=();
    foreach $e (@list){ unless (($seen{$e->{'node1'}.">".$e->{'node2'}})||($seen{$e->{'node2'}.">".$e->{'node1'}})){ push @return, $e; $seen{$e->{'node1'}.">".$e->{'node2'}}=1 } }
    return @return;
}
# End function: unique

# Start function: parse
sub parse{																													# parse numbers in sequences of vlans
	my ($p)=@_; my ($map,$n,$x)=('','','');
	foreach $n (parse_line(",",0, $p)){ if($n=~m/(\d*)\-(\d*)/){ for ($x=$1;$x<=$2;$x++){ $map .= "$x," } }else{ $map .= "$n," } };
    return $map;
}
# End function: parse

# Start function: print_row
sub print_row{																												# writes one row in Excel
	$r++; @buffer=();
	foreach $x (@{$header_col->{$n}}){ if(exists($$obj{$x})){ push @buffer,$obj->{$x} }else{ push @buffer,'' } }
	$xls->write_row($r,0,\@buffer); undef @buffer;
}
# End function: print_row

# Start function: printXL
sub printXL{																												# Write objects to Excel sheets
	use Excel::Writer::XLSX;																								# Allows 1M rows (use Spreadsheet::WriteExcel for Excel 2007 (limit of 65k rows)
	my ($Excel)=@_; $z=1;
	$workbook=Excel::Writer::XLSX->new($Excel) or die "Couldn't open file $Excel";											# Allows for 1 Million rows
	#$workbook=Spreadsheet::WriteExcel->new($ExcelFile."xls") or die "Couldn't open file $ExcelFile.xls";					# Use for Excel 2007 compatibility (limit of 65k rows)
	$header_fmt=$workbook->add_format(); $header_fmt->set_bold(); $header_fmt->set_size(12); $header_fmt->set_color('blue'); $header_fmt->set_font('Consolas');
	$global_fmt=$workbook->add_format(); $global_fmt->set_size(10); $global_fmt->set_font('Consolas');
	#$workbook->set_optimization();
	($n,$r,$x,$c,$map)=('',0,'',0,''); ($src,$dst,$src_t,$dst_t,$srvc,$srvc_t)=(',',',',',',',',',',',');
	foreach $n (sort keys %{$tb}){																							# Create and populate sheets
		if(UNIVERSAL::isa($tb->{$n},'ARRAY')){																				# For array type objects
			$c=scalar(@{$tb->{$n}}); 
			if($c){
				$xls=$workbook->add_worksheet($n); $r=0; $xls->write_row('A1', \@{$header_col->{$n}},$header_fmt); 
				foreach $obj (@{$tb->{$n}}){
					if($r<1048575){ print_row }
					else{  																									# Write new file
						$now=time(); &dbg("#@# Writing excel            \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
						$xls->set_zoom('80'); $xls->freeze_panes(1,0); $xls->set_column('A:O',20); if($r>0){ $xls->autofilter(0,0,$r-1,scalar(@{$header_col->{$n}})-1) }
						$xls=$workbook->add_worksheet("$n$z"); $z++; $r=0; $xls->write_row('A1', \@{$header_col->{$n}},$header_fmt); 
						print_row;
					} 
				}
				$xls->set_zoom('80'); $xls->freeze_panes(1,0); $xls->set_column('A:O',20); if($r>0){ $xls->autofilter(0,0,$r-1,scalar(@{$header_col->{$n}})-1) }
		}	}
		elsif(scalar(%{$tb->{$n}})){																						# For hash type objects
			$obj=$tb->{$n};
			$xls=$workbook->add_worksheet($n); $r=0; $xls->write_row('A1', \@{$header_col->{$n}}, $header_fmt);
			foreach $x (sort keys %{$obj}){																					# first column with hash key. If hash value has '|' split into columns
				$r++; $xls->write_string($r,0,$x,$global_fmt); $c=1; foreach $p (split(/\|/,$obj->{$x})){ if(defined $p){ $xls->write_string($r,$c,$p,$global_fmt) }; $c++ }
			}
			$xls->set_zoom('80'); $xls->freeze_panes(1,0); $xls->set_column('A:O',20); if($r>0){ $xls->autofilter(0,0,$r-1,scalar(@{$header_col->{$n}})-1) }
	}	}	
	$now=time(); &dbg("#@# Finish writing excel     \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
	$workbook->close(); 
	$now=time(); &dbg("#@# Finish saving excel      \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
}
# End function: printXL

# Start function: expand
sub expand{																													# Replace ACL objects by its members
	$obj2->{'L3'}=~s/^$src([^\w-]|$)/$src_mm$1/ unless ($src_m eq '*');
	$obj2->{'L3'}=~s/-> $dst$/-> $dst_mm/ unless ($dst_m eq '*');
	if(exists($$obj2{'L4'})){
		$obj2->{'L4'}=~s/(^|[^\w-])$prot([^\w-]|$)/$1$prot_mm$2/ unless ($prot_m eq '*');
		$obj2->{'L4'}=~s/(^|[^\w-])$srvc([^\w-]|$)/$1$srvc_mm$2/ unless ($srvc_m eq '*');
		$obj2->{'L4'}=~s/(^|[^\w-])$srvc2([^\w-]|$)/$1$srvc2_mm$2/ unless ($srvc2_m eq '*');
		$obj2->{'L4'}=~s/:#/#/g; $obj2->{'L4'}=~s/:eq#/#/g; $obj2->{'L4'}=~s/:$//g; $obj2->{'L4'}=~s/(tcp|udp):tcp-udp/$1/g; 
		if($obj2->{'L4'}=~m/^([^:]+):([^:#]+)(:|#)(.*)$/){ if($1 eq $2){ $obj2->{'L4'}="$2$3$4" } } }
	if(($src_mm eq $dst_mm)&&(lc($src_mm)!~m/^any|\*$/)&&$expand){ $obj2->{'status'}='N/A' }								# Mark when src equals dst
	#&dbg(Dumper("Expanded ACL:",$obj2));
	push @xtend,$obj2; $obj2={}
}
# End function: expand

# Start function: expandNAT
sub expandNAT{																												# Replace NAT objects by its members
	$obj2->{'L3'}=~s/^$src([^\w-]|$)/$src_mm$1/ unless ($src_m eq '*');
	$obj2->{'L3'}=~s/([^\w-]|^)$dst <>/$1$dst_mm <>/ unless ($dst_m eq '*');
	$obj2->{'L3'}=~s/<> $src_t([^\w-]|$)/<> $src_t_mm$1/ unless ($src_t_m eq '*');
	$obj2->{'L3'}=~s/<> (.*) -> $dst_t([^\w-]|$)/<> $1 -> $dst_t_mm$2/ unless ($dst_t_m eq '*');
	if(exists($$obj2{'L4'})){
		$obj2->{'L4'}=~s/(^|[^\w-])$prot([^\w-]|$)/$1$prot_mm$2/ unless ($prot_m eq '*');
		$obj2->{'L4'}=~s/(^|[^\w-])$srvc <>/$1$srvc_mm <>/ unless ($srvc_m eq '*');
		$obj2->{'L4'}=~s/<> $srvc_t([^\w-]|$)/<> $srvc_t_mm$1/ unless ($srvc_t_m eq '*');
		$obj2->{'L4'}=~s/:#/#/g; $obj2->{'L4'}=~s/:eq#/#/g; $obj2->{'L4'}=~s/:$//g; $obj2->{'L4'}=~s/(tcp|udp):tcp-udp/$1/g;
		if($obj2->{'L4'}=~m/^([^:]+):([^:#]+)(:|#)(.*)$/){ if($1 eq $2){ $obj2->{'L4'}="$2$3$4" } } }
	#&dbg(Dumper("Expanded NAT:",$obj2));
	push @xtend,$obj2; $obj2={}
}
# End function: expandNAT

# Start function: execute
sub execute{																												# Main processing/parsing function
	use XML::XPath;																											# For XML file processing
	use XML::XPath::XMLParser;
	use Devel::Size qw(size total_size);
	GetOptions ("o=s"=>\$ExcelFile,"x=s"=>\$x,"e"=>\$expand);									 							# Get options: -o LanMnhmtXL.xlsx -x nnnnnnnnnnnnnn -e
	if($x=~m/^[0-1]{14}$/){
	  ($DEBUG,$EXCEL,$MYSQL,$Ignored,$ExpandGroups,$TranslateNames,$XLcode,$NotFound,$NetList,$LayPhysical,$LayLogical,$LayNetwork,$LayFlow,$Covered)=($x=~/([0-1])/g);
	  if($LayNetwork){$NetList=1}; if($NetList||$LayPhysical||$LayLogical||$LayNetwork||$LayFlow||$Covered){$XLcode=1}; if($XLcode){$TranslateNames=1}; if($NotFound){$ExpandGroups=1; $TranslateNames=1};
	};
	foreach $file (@ARGV){																									# Process arguments
		&dbg("arg: $file\n");
		if(-d $file) { 																										# If directory get each file
			opendir(DIR, $file) or warn "Unable to access directory $file"; @dir=readdir(DIR); foreach (@dir) { push @configs,$file.'\\'.$_ unless !(-f $file.'\\'.$_) } }
		elsif(-f $file) { push @configs,$file }
	}
	undef $file; &dbg(Dumper(@configs));
	foreach $file (@configs){																								# Process each file
		($count,$flag_more,$flag,$map,$obj,$obj2)=(1,0,'','',{},{}); @objhost=();											# Reset vars
		$host="unknown" unless ($host)=$file=~/([^\\]+)\+.+$/; &dbg("File: $file, Hostname: $host\n");
		$xlate->{"t:$host:L3:any"}='0.0.0.0/0'; $xlate->{"t:$host:L4:any"}='#0';											# Default translations
		($ext)=$file=~m/([^.]+)$/;
		if($ext=~/xml/){
			$xml=XML::XPath->new(filename=>$file);
			if($file=~/network_objects/){																					# Network Objects file
				$node=$xml->find("//network_object");
				foreach $row ($node->get_nodelist()){
					($sub,$cell)=(undef,undef);
					$map=$row->find("Class_Name")->string_value();
					if($map=~/network_object_group/){
						$sub=$xml->find("./members/reference",$row);
						if(!$expand){
							%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$map,'desc'=>$row->find("comments")->string_value());
							foreach $cell ($sub->get_nodelist()){
								$x=$cell->find("Name")->string_value(); $obj->{'member'} .= $x.',';
								$members->{"m:$host:L3:".$obj->{'name'}} .= $x.' ';											# For group expansion
							}
							if(exists($$obj{'member'})){ $obj->{'member'}=~s/,$//;	}										# Format correction
							&dbg(Dumper($obj)); push @objects, $obj; $obj={}
						}else{
							foreach $cell ($sub->get_nodelist()){
								%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$map,'member'=>$cell->find("Name")->string_value(),'desc'=>$row->find("comments")->string_value());
								$members->{"m:$host:L3:".$obj->{'name'}} .= $obj->{'member'}.' ';							# For group expansion
								&dbg(Dumper($obj)); push @objects, $obj; $obj={}
					}	}	}
					else{
						$more=0;
						%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$map,'desc'=>$row->find("comments")->string_value());
						if($row->find("ipaddr_first")->string_value()){	$obj->{'L3'}=$row->find("ipaddr_first")->string_value().'-'.$row->find("ipaddr_last")->string_value(); $more=1 }
						elsif($row->find("netmask")->string_value()){ $obj->{'L3'}=$row->find("ipaddr")->string_value().'/'.unpack('%32b*',inet_aton($row->find("netmask")->string_value())); $more=1 }
						elsif($row->find("ipaddr")->string_value()){ $obj->{'L3'}=$row->find("ipaddr")->string_value().'/32'; $more=1 }
						elsif($row->find("base/Name")->string_value()){
							$obj->{'member'}=$row->find("base/Name")->string_value().'~'.$row->find("exception/Name")->string_value(); # case of group with exclusions
							$members->{"m:$host:L3:".$obj->{'name'}}=$obj->{'member'};										# For group expansion
						}
						if($more){ $xlate->{"t:$host:L3:".lc($obj->{'name'})}=$obj->{'L3'} };								# For name translation
						if($row->find("manual_encdomain/Name")->string_value()){
							$obj->{'vpn'}=$row->find("manual_encdomain/Name")->string_value();
							$domain->{"d:$host:".$obj->{'vpn'}}=$obj->{'name'}	}											# For objects used (VPN domain)
						$sub=$xml->find("./interfaces/interfaces",$row);
						if(!$expand){
							if(exists($$obj{'L3'})){ $obj->{'L3'} .= ',' };
							foreach $cell ($sub->get_nodelist()){
								$x=$cell->find("ipaddr")->string_value().'/'.unpack('%32b*',inet_aton($cell->find("netmask")->string_value())); $obj->{'L3'} .= $x.',';
								$members->{"m:$host:L3:".$obj->{'name'}} .= $x.' ';											# For group expansion
								if($cell->find("security/netaccess/allowed/Name")->string_value()){
									$obj->{'L1'} .= $cell->find("security/netaccess/allowed/Name")->string_value().',';
									push @used,lc("$host:L3:".$obj->{'L1'}) }												# For objects used (antispoof domain)
							}
							if($row->find("manual_encdomain/Name")->string_value()){
								$obj->{'vpn'}=$row->find("manual_encdomain/Name")->string_value();
								$domain->{"d:$host:".$obj->{'vpn'}}=$obj->{'name'} }										# For objects used (VPN domain)
							&dbg(Dumper($obj)); push @objects, $obj; $obj={}
						}else{
							&dbg(Dumper($obj));	push @objects, $obj; $obj={};
							foreach $cell ($sub->get_nodelist()){
								%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$map,
										'L3'=>$cell->find("ipaddr")->string_value().'/'.unpack('%32b*',inet_aton($cell->find("netmask")->string_value())),'desc'=>$row->find("comments")->string_value());
								$members->{"m:$host:L3:".$obj->{'name'}} .= $obj->{'L3'}.' ';								# For group expansion
								if($row->find("manual_encdomain/Name")->string_value()){
									$obj->{'vpn'}=$row->find("manual_encdomain/Name")->string_value();
									$domain->{"d:$host:".$obj->{'vpn'}}=$obj->{'name'} }									# For objects used (VPN domain)
								if($cell->find("security/netaccess/allowed/Name")->string_value()){
									$obj->{'L1'}=$cell->find("security/netaccess/allowed/Name")->string_value();
									push @used,lc("$host:L3:".$obj->{'L1'}) }												# For objects used (antispoof domain)
								&dbg(Dumper($obj)); push @objects, $obj; $obj={}
			}	}	}	}	}
			elsif($file=~/services/){																						# Services File
				$node=$xml->find("//service");
				foreach $row ($node->get_nodelist()){
					($sub,$cell)=(undef,undef);
					$map=$row->find("Class_Name")->string_value();
					if($map=~/service_group/){
						$sub=$xml->find("./members/reference",$row);
						if(!$expand){
							%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$map,'desc'=>$row->find("comments")->string_value());
							foreach $cell ($sub->get_nodelist()){
								$x=$cell->find("Name")->string_value(); $obj->{'member'} .= $x.',';
								$members->{"m:$host:L4:".$obj->{'name'}} .= $x.' ';											# For group expansion
							}	
							&dbg(Dumper($obj)); push @objects, $obj; $obj={}
						}else{
							foreach $cell ($sub->get_nodelist()){
								%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$map,'desc'=>$row->find("comments")->string_value(),'member'=>$cell->find("Name")->string_value());
								$members->{"m:$host:L4:".$obj->{'name'}} .= $obj->{'member'}.' ';							# For group expansion
								&dbg(Dumper($obj)); push @objects, $obj; $obj={}
					}	}	}
					else{
						$name=$row->find("Name")->string_value();
						%{$obj}=('origin'=>$host,'name'=>$name,'class'=>'CHK-'.$map,'desc'=>$row->find("comments")->string_value());
						$obj->{'L4'}=$row->find("type")->string_value();
						if($row->find("protocol")->string_value()){ $obj->{'L4'} .= ':'.$row->find("protocol")->string_value() }
						if($row->find("port")->string_value()){ $obj->{'L4'} .= '#'.$row->find("port")->string_value() }
						if($row->find("icmp_type")->string_value()){ $obj->{'L4'} .= '_type'.$row->find("icmp_type")->string_value() }
						if($row->find("icmp_code")->string_value()){ $obj->{'L4'} .= '#'.$row->find("icmp_code")->string_value() }
						if($row->find("src_port")->string_value()){ $obj->{'L4'} .= '&src#'.$row->find("src_port")->string_value() }
						if($map=~/(other|dcerpc|icmp)_service/i){ $obj->{'L4'}=~s/^($1)/$1:$name/i } 
						$obj->{'L4'}=~s/^(#|:)//; $obj->{'L4'}=~s/#>/:gt#/; $obj->{'L4'}=~s/#</:lt#/;						# Format adaptation
						$xlate->{"t:$host:L4:".lc($obj->{'name'})}=$obj->{'L4'};											# For name translation
						&dbg(Dumper($obj)); push @objects, $obj; $obj={}
			}	}	}
			elsif($file=~/communities/){																					# VPN Communities File
				$node=$xml->find("//communitie");
				foreach $row ($node->get_nodelist()){
					($sub,$cell,$srvc)=(undef,undef,'');
					%{$obj}=('origin'=>$host,'name'=>$row->find("Name")->string_value(),'class'=>'CHK-'.$row->find("Class_Name")->string_value(),'desc'=>$row->find("comments")->string_value());
					if($row->find("satellite_gateways/satellite_gateways/Name")->string_value()){ $obj->{'L3'}=$row->find("satellite_gateways/satellite_gateways/Name")->string_value() }
					$obj->{'desc'} .= '#type:'.$row->find("type")->string_value();
					if($row->find("topology")->string_value()){ $obj->{'desc'} .= '#topology:'.$row->find("topology")->string_value() }
					if($row->find("tunnel_granularity")->string_value()){ $obj->{'desc'} .= '#'.$row->find("tunnel_granularity")->string_value() }
					$obj->{'desc'} .= '#P1-Hash:'.$row->find("ike_p1/ike_p1_hash_alg")->string_value();
					$obj->{'desc'} .= '#P1-Encrypt:'.$row->find("ike_p1/ike_p1_enc_alg")->string_value();
					$obj->{'desc'} .= '#P1-LifetimeSec:'.$row->find("ike_p1/ike_p1_rekey_time")->string_value();
					$obj->{'desc'} .= '#P1-DHgroup:'.$row->find("ike_p1/ike_p1_dh_grp/Name")->string_value();
					if($row->find("ike_p1/ike_p1_use_aggressive")->string_value() eq 'TRUE'){ $obj->{'desc'} .= '#Agressive' }
					if($row->find("ike_p1/ike_p1_use_shared_secret")->string_value() eq 'TRUE'){ $obj->{'desc'} .= '#SharedSecret' }
					$obj->{'desc'} .= '#P2-Hash:'.$row->find("ike_p2/ike_p2_hash_alg")->string_value();
					$obj->{'desc'} .= '#P2-Encrypt:'.$row->find("ike_p2/ike_p2_enc_alg")->string_value();
					if($row->find("ike_p2/ike_p2_use_rekey_kbytes")->string_value() eq 'TRUE'){ $obj->{'desc'} .= '#P2-LifetimeKB:'.$row->find("ike_p2/ike_p2_rekey_kbytes")->string_value() }
					else{ $obj->{'desc'} .= '#P2-LifetimeSec:'.$row->find("ike_p2/ike_p2_rekey_time")->string_value() }
					if($row->find("ike_p2/ike_p2_use_pfs")->string_value() eq 'TRUE'){ $obj->{'desc'} .= '#P2-DHgroup:'.$row->find("ike_p2/ike_p2_pfs_dh_grp/Name")->string_value() }
					if($row->find("ike_p2/ike_p2_use_subnets")->string_value() eq 'TRUE'){ $obj->{'desc'} .= '#UseSubnets' }
					$sub=$xml->find("./exclude_srv/exclude_srv",$row);
					foreach $cell ($sub->get_nodelist()){
						$srvc=$cell->find("Name")->string_value(); $obj->{'desc'} .= "#Exclude:$srvc"; push @used,lc("$host:L4:$srvc") }		# For objects used (antispoof domain)
					&dbg(Dumper($obj)); push @objects, $obj; $obj={}
			}	}
			elsif($file=~/users/){																							# Users (Tacacs and Radius servers)
				$node=$xml->find("//user");
				foreach $row ($node->get_nodelist()){
					($sub,$cell)=(undef,undef);
					%{$obj}=('origin'=>$host,'class'=>'CHK-'.$row->find("Class_Name")->string_value(),'desc'=>'Tacacs/Radius information');
					if($row->find("radius_server/Name")->string_value() ne 'Any'){ $obj->{'L3'}=$row->find("radius_server/Name")->string_value() }
					elsif($row->find("tacacs_server/Name")->string_value() ne 'Any'){ $obj->{'L3'}=$row->find("tacacs_server/Name")->string_value() }
					else{ $obj={} };
					if(!$obj->{'L3'}){ $obj={} };
					if(scalar %{$obj}&&!exists($$user{'u:'.$obj->{'L3'}})){ 
						$obj->{'name'}=$obj->{'L3'}; &dbg(Dumper($obj)); push @objects, $obj; 
						push @used,lc("$host:L3:".$obj->{'L3'}); $user->{'u:'.$obj->{'L3'}}=$obj->{'L3'}; $obj={}			# For objects use (authentication servers)
			}	}	}
			elsif($file=~/\+(.*)_Security_Policy/){																			# Security Policy file
				$name=$1; $node=$xml->find("//fw_policie/rule/rule");
				foreach $row ($node->get_nodelist()){
					($rule,$status,$src,$dst,$srvc,$action,$vpn,$desc,$flag)=('','','','','','','','','');
					$rule=$row->find("Rule_Number")->string_value();
					if($rule=~/^\d+?$/){
						$action=$row->find("action/action/Name")->string_value();
						$status=$row->find("disabled")->string_value();
						$desc  =$row->find("comments")->string_value(); 
						if($row->find("name")->string_value()){ $desc .= "#Name:".$row->find("name")->string_value() };
						$desc .= "#uuid:".$row->find("Rule_UUID")->string_value();
						$vpn=$row->find("through/members/reference/Name")->string_value(); push @used,(lc("$host:vpn:$vpn"));
						($sub,$cell,@dst)=(undef,undef,()); $sub=$xml->find("./dst/members/reference",$row); if($row->find("dst/op")->string_value() eq "not in"){ $flag='~' }else{ $flag='' };
						foreach $cell ($sub->get_nodelist()){ $dst=$flag.$cell->find("Name")->string_value(); push(@dst, $dst); if($flag){ $members->{"m:$host:L3:".$dst}=$cell->find("Name")->string_value() } }
						($sub,$cell,@srvc)=(undef,undef,()); $sub=$xml->find("./services/members/reference",$row); if($row->find("services/op")->string_value() eq "not in"){ $flag='~' }else{ $flag='' };
						foreach $cell ($sub->get_nodelist()){ $srvc=$flag.$cell->find("Name")->string_value(); push(@srvc, $srvc); if($flag){ $members->{"m:$host:L4:".$srvc}=$cell->find("Name")->string_value() } }
						($sub,$cell,@src)=(undef,undef,()); $sub=$xml->find("./src/members/reference",$row); if($row->find("src/op")->string_value() eq "not in"){ $flag='~' }else{ $flag='' };
						foreach $cell ($sub->get_nodelist()){ $src=$flag.$cell->find("Name")->string_value(); push(@src, $src); if($flag){ $members->{"m:$host:L3:".$src}=$cell->find("Name")->string_value() } }
						($sub,$cell)=(undef,undef); $sub=$xml->find("./src/compound/compound",$row); if($row->find("src/op")->string_value() eq "not in"){ $flag='~' }else{ $flag='' };
						foreach $cell ($sub->get_nodelist()){ $src=$flag.$cell->find("Name")->string_value().'_@_'.$cell->find("at/Name")->string_value(); push(@src, $src) }
						($sub,$cell,$flag)=(undef,undef,''); $sub=$xml->find("./time/time",$row);
						foreach $cell ($sub->get_nodelist()){ $flag .= $cell->find("Name")->string_value() }
						if(!$expand){
							%{$obj}=('origin'=>$host,'name'=>"SEC-$name rule $rule",'class'=>'acl','status'=>($status eq 'false')?'on':'off','action'=>$action,'vpn'=>$vpn,'desc'=>$desc);
							if($flag ne 'Any'){ $obj->{'status'} .= 'Timed'; $obj->{'desc'} .= "#Time:$flag" }
							foreach $src (@src){ $obj->{'L3'} .= $src.','; push @used,(lc("$host:L3:$src")) } $obj->{'L3'} .= ' -> ';
							foreach $dst (@dst){ $obj->{'L3'} .= $dst.','; push @used,(lc("$host:L3:$dst")) } 
							foreach $srvc (@srvc){ $obj->{'L4'} .= $srvc.','; push @used,(lc("$host:L4:$srvc")) }
							&dbg(Dumper($obj)); push @objects, $obj; $obj={}
						}else{
							foreach $src (@src){ push @used,(lc("$host:L3:$src"));
							foreach $srvc (@srvc){ push @used,(lc("$host:L4:$srvc"));
							foreach $dst (@dst){ push @used,(lc("$host:L3:$dst"));
								%{$obj}=('origin'=>$host,'name'=>"SEC-$name rule $rule",'class'=>'acl','status'=>($status eq 'false')?'on':'off','L3'=>$src.' -> '.$dst,'L4'=>$srvc,'action'=>$action,'vpn'=>$vpn,'desc'=>$desc);
								if($flag ne 'Any'){ $obj->{'status'} .= 'Timed'; $obj->{'desc'} .= "#Time:$flag" }
								&dbg(Dumper($obj)); push @objects, $obj; $obj={}
			}	}	}	}}}}
			elsif($file=~/\+(.*)_NAT_Policy/){																					# NAT file
				$name=$1; $node=$xml->find("//fw_policie/rule_adtr/rule_adtr");
				foreach $row ($node->get_nodelist()){
					($rule,$src,$dst,$srvc,$src_t,$dst_t,$srvc_t)=('','','','','','','');
					$rule=$row->find("Rule_Number")->string_value();
					if($rule=~/^\d+?$/){
						$src   =$row->find("src_adtr/src_adtr/Name")->string_value();
						$src_t =$row->find("src_adtr_translated/reference/Name")->string_value();
						$dst   =$row->find("dst_adtr/dst_adtr/Name")->string_value();
						$dst_t =$row->find("dst_adtr_translated/reference/Name")->string_value();
						$srvc  =$row->find("services_adtr/services_adtr/Name")->string_value();
						$srvc_t=$row->find("services_adtr_translated/reference/Name")->string_value();
						$desc  =$row->find("comments")->string_value(); if($row->find("name")->string_value()){ $desc .= "#Name:".$row->find("name")->string_value() };
						%{$obj}=('origin'=>$host,'name'=>"NAT-$name rule $rule",'class'=>'nat','status'=>($row->find("disabled")->string_value() eq 'false')?'on':'off',
								'L3'=>$src.' -> '.$dst.' <> '.$src_t.' -> '.$dst_t,'L4'=>$srvc.' <> '.$srvc_t,'desc'=>$desc);
						push @used,(lc("$host:L3:$src"),lc("$host:L3:$src_t"),lc("$host:L3:$dst"),lc("$host:L3:$dst_t")); push @used,(lc("$host:L4:$srvc"),lc("$host:L4:$srvc_t")); # Use of object check
						&dbg(Dumper($obj));; push @objects, $obj; $obj={}
			}	}	}
			undef $xml; undef $node
		}
		elsif($ext=~/cisco/){																								# Parse Cisco (Switch/Router/Firewall)
		open FILE, "< $file" or warn "Can't open $file : $!";
		while (<FILE>){
			$z=$_; chomp $z; &dbg("line:$z\n");
			($src,$dst,$src_t,$dst_t,$prot,$srvc,$srvc2,$srvc_t)=('','','','','','','','');
			if($z=~m/^hostname\s+(.*)$/){																					# Hostname/Names/Description sections
				$host=$1; $xlate->{"t:$host:L3:any"}='0.0.0.0/0'; $xlate->{"t:$host:L4:any"}='#0';							# Default translations
				&dbg("hostname:$1\n") }
			elsif($z=~m/^name\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\S+)(.*)$/){										# 'name' section
				%{$obj}=('origin'=>$host,'name'=>$2,'class'=>'name','L3'=>$1); $dl=__LINE__;
				$xlate->{"t:$host:L3:".lc($2)}=$1;																			# For name translation
				if($3=~m/^\s+description\s+(.*)$/){ $obj->{'desc'}='#'.$1;} }
			elsif($z=~m/^ip host\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)$/){										#'ip host' section
				%{$obj}=('origin'=>$host,'name'=>$1,'class'=>'name','L3'=>$2); $dl=__LINE__; $name=$1; $more=$3;
				while($more=~m/^\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)$/){
					&dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost,$obj; $obj={};
					%{$obj}=('origin'=>$host,'name'=>$name,'class'=>'name','L3'=>$1); $dl=__LINE__; $more=$2;
			}	}
			elsif($z=~m/^\s+description\s+(.*)$/){																			# 'description' lines
				$desc=$1; $dl=__LINE__;
				if($obj=pop @objhost){
					if(($obj->{'class'} eq 'interface')||($obj->{'class'} eq 'vlan')){ $obj->{'desc'}='# '.$desc; $desc=''; } # If description of interfaces or vlans (other ignored)
					else{ $obj->{'desc'}.='# '.$desc; $desc=''; push @objhost,$obj; $obj={} } }
				else{ $obj={} } }
			elsif(($z=~m/^object-group\s+(\S+)\s+(\S+)(.*)$/)||($z=~m/^object\s+(\S+)\s+(\S+)(.*)$/)){						# Objects (firewall) section
				$name=$2; $map=$1; $desc=''; $member=''; $aux='' unless ($aux)=$3=~m/\s+(\S+)/; if($aux){ $p="$aux:" }else{ $p='' }
				if(!$expand){ $obj->{'origin'}=$host; $obj->{'name'}=$name; $obj->{'class'}='ASA-'.$map; $obj->{'desc'}=$desc; $flag_obj='obj-def'; $dl=__LINE__; }
				&dbg("Name:$name Class:$map Prot:$p\n")
			}
			elsif(($z=~m/^\s+(group|icmp|network|protocol|port|service)-object\s+(.*)$/)||($z=~m/^\s+(host|subnet|object|service)\s+(.*)$/)){
				$flag=''; $aux='';
				if(!$expand){ $obj=pop @objhost }else{ $obj->{'origin'}=$host; $obj->{'name'}=$name; $obj->{'class'}='ASA-'.$map; $obj->{'desc'}=$desc }; $dl=__LINE__;
				if($z=~m/^\s+group-object\s+(\S+).*$/){ $aux=$1; if($map eq 'service'){ $flag='L4' }else{ $flag='L3' } }
				elsif($z=~m/^\s+(network-object )?host\s+(\S+).*$/){ $aux=$2.'/32'; $flag='L3' }
				elsif($z=~m/^\s+(network-object )?object\s+(\S+).*$/){ $aux=$2; $flag='L3' }
				elsif($z=~m/^\s+(network-object|subnet)\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){ $aux=$2.'/'.unpack('%32b*',inet_aton($3)); $flag='L3' }	# mask to prefix
				elsif($z=~m/^\s+icmp-object\s+(\S+).*$/){ $aux="icmp:$1"; $flag='L4' }
				elsif($z=~m/^\s+protocol-object\s+(\S+).*$/){ $aux=$1; $flag='L4' }
				elsif(($z=~m/^\s+port-object\s+(.*)$/)||($z=~m/^\s+service-object\s+(.*)$/)||($z=~m/^\s+service\s+(.*)$/)){
					$more=$1; if(($z=~m/^\s+service-object\s+(\S+)(.*)$/)||($z=~m/^\s+service\s+(\S+)(.*)$/)){ $aux="$p$1:"; $more=$2 }else{ $aux=$p; }
					if($more=~m/^\s*source\s+(\S+)(.*)$/){ $aux='&src:'.$aux; $more=$2 }
					if($more=~m/^\s*destination\s+(.*)$/){ $more=$1 }
					if($more=~m/^\s*(eq|neq|lt|gt)\s+(\S+).*$/){ $aux .= "$1#$2" }
					elsif($more=~m/^\s*range\s+(\S+)\s+(\S+).*$/){ $aux .= "#$1-$2" }
					elsif($more=~m/^\s*(\S+).*$/){ if($aux eq 'icmp'){ $aux .= "$1" }else{ $aux .= "#$1" } }
					$flag='L4' }
				else{ &dbg("*NOT-MATCHED* $z\n") };
				if($flag){ if(!$expand){ $obj->{$flag} .= $aux.',' }else{ $obj->{$flag}=$aux } }; $member=$aux;
				if($map eq 'network'){ $members->{"m:$host:L3:".$name} .= $member.' ' } else { $members->{"m:$host:L4:".$name} .= $member.' ' } 	# For group expansion
			}
			elsif(($z=~m/^logging host\s+(\S+)\s+(udp|tcp).*$/)||($z=~m/^logging host\s+\S+\s+(\S+).*$/)||($z=~m/^logging server\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)||($z=~m/^logging\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)){
				%{$obj}=('origin'=>$host,'name'=>"logging $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }							# Management IPs section
			elsif($z=~m/^snmp-server host\s+\S+\s+(\S+)\s+community.*$/){ %{$obj}=('origin'=>$host,'name'=>"snmp $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^snmp-server host\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){ %{$obj}=('origin'=>$host,'name'=>"snmp $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^aaa-server\s+\S+\s+\(\S+\) host (\S+).*$/){ %{$obj}=('origin'=>$host,'name'=>"aaa $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^tacacs-server host (\S+).*$/){ %{$obj}=('origin'=>$host,'name'=>"aaa $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^radius-server host (\S+).*$/){ %{$obj}=('origin'=>$host,'name'=>"aaa $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^ntp server (\S+).*$/){ %{$obj}=('origin'=>$host,'name'=>"ntp $1",'class'=>'mngmt','L3'=>$1.'/32','desc'=>$z); $dl=__LINE__ }
			elsif(($z=~m/^interface\s([a-zA-Z\d\/\.-]+)$/i)||($z=~m/^interface\s([a-zA-Z\d\/\.-]+ [\d\/\.-]+)$/i)){			# Interface section
				$if=lc($1); $if=~s/\s+//g; %{$obj}=('origin'=>$host,'name'=>$if,'class'=>'interface','desc'=>''); $dl=__LINE__;
				if($if=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $ifs=$1.$2; $obj->{'L1'}=$ifs; if($1 eq 'vl'){ $obj->{'vlan'}=$2 } } }
			elsif($z=~m/^\s+ip address\s+.*$/){
				if($z=~m/^\s+ip address\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)$/){ $ip=$1; $more=$3; $n=unpack('%32b*',inet_aton($2)) }		# mask to prefix
				elsif($z=~m/^\s+ip address\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d+)(.*)$/){ $ip=$1; $more=$3; $n=$2 }
				$obj=pop @objhost;
				if($more=~/\s+secondary.*/){ $obj->{'L3'} .= "$ip/$n|secIP,"; $dl=__LINE__ }
				elsif($more=~/\s+standby\s+(\S+).*/){ $obj->{'L3'} .= "$ip/$n,$1/$n|standby,"; $dl=__LINE__ }
				else{ $obj->{'L3'} .= "$ip/$n,"; $dl=__LINE__ } }
			elsif($z=~m/^\s+channel-group (\d+).*$/){ $obj=pop @objhost; $obj->{'member'}="Po$1"; $dl=__LINE__ }
			elsif($z=~m/^\s+vpc (\d+).*$/){ $obj=pop @objhost; $obj->{'member'}="vp$1"; $dl=__LINE__ }
			elsif($z=~m/^\s+fex associate (\d+).*$/){ $obj=pop @objhost; $obj->{'name'}.=" FEX-$1"; $dl=__LINE__ }
			elsif($z=~m/^\s+switchport trunk native vlan\s+(.*)$/){ $obj=pop @objhost; $obj->{'vlan'}=$1; $dl=__LINE__ }
			elsif($z=~m/^\s+switchport access vlan\s+(.*)$/){ $obj=pop @objhost; $obj->{'vlan'}=$1; $dl=__LINE__ }
			elsif($z=~m/^\s+switchport trunk allowed vlan( add)?\s+(.*)$/){ 
				$obj=pop @objhost; $map=&parse($2); if($1){ $obj->{'vlan'}.=$map; }else{ $obj->{'vlan'}=$map; }; $flag='trunk-allowed'; $dl=__LINE__ }
			elsif($z=~m/^\s+switchport mode\s+(.*)$/){ $obj=pop @objhost; $obj->{'desc'} .= '#mode '.$1; if(($1 eq 'trunk')&&($flag ne 'trunk-allowed')){ $obj->{'vlan'}=$1 }; $dl=__LINE__ }
			elsif($z=~m/^\s+spanning-tree\s+(.*)$/){ $obj=pop @objhost; $obj->{'desc'} .= '#stp:'.$1; $dl=__LINE__ }
			elsif($z=~m/^\s+nameif\s+(.*)$/){ $obj=pop @objhost; $obj->{'L1'}=$1; $dl=__LINE__ }
			elsif($z=~m/^\s+security-level\s+(.*)$/){ $obj=pop @objhost; $obj->{'desc'} .= '#sec-level '.$1; $dl=__LINE__ }
			elsif($z=~m/^\s+(standby|hsrp|vrrp|glbp)\s+\d{1,3}\s+ip\s+(\S+).*$/){ $obj=pop @objhost; $obj->{'L3'} .= $2.'|'.$1.','; $dl=__LINE__ }	# FHRP section
			elsif($z=~m/^\s+(standby|hsrp|vrrp|glbp)\s+\d{1,3}.*$/){ $map=$1; $flag='section-fhrp-cfg'; &dbg("FHRP cfg section\n") }
			elsif(($flag eq 'section-fhrp-cfg')&&($z=~m/^\s+ip\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)){ $obj=pop @objhost; $obj->{'L3'} .= $1.'|'.$map.','; $dl=__LINE__ }
			elsif($z=~m/^Interface\s+Grp\s.*\sState.*$/){ $flag='section-fhrp-status'; &dbg("FHRP status section\n") }
			elsif(($flag eq 'section-fhrp-status')&&(($z=~m/^\S+\s+\d+\s+\d+\s+\S?\s+(\S+)\s+\S+\s+\S+\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*(\(conf\))*$/)		# standby, hsrp
												   ||($z=~m/^\S+\s+\d+\s+\S+\s+\d+\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\S+\s+\S+\s*(\(conf\))*$/)		# glbp
												   ||($z=~m/^\S+\s+\d+\s+\d+\s+\d+\s+\S?\s+(\S+)\s+\S+\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*(\(conf\))*$/))){	# vrrp
				$x=$1; $ip=$2;
				foreach $obj2 (@objhost){
					if(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')&&exists($$obj2{'L3'})&&($obj2->{'L3'}=~m/^.*,$ip\|(standby|hsrp|vrrp|glbp),.*$/)){
							$obj2->{'L3'}=~s/$ip\|(standby|hsrp|vrrp|glbp)/$ip\|$1:$x/; &dbg(Dumper(__LINE__,$obj2))
			}	}	}
			elsif(($z=~m/^Interface\s+Filter\s+State\s+Upper\s+Lower\s+Current.*$/)
				||($z=~m/^Interface\s+Filter\s+State\s+Broadcast\s+Multicast\s+Level.*$/)
				||($z=~m/^Port\s+UcastSupp \%\s+McastSupp \%\s+BcastSupp \%\s+TotalSuppDiscards.*$/))
				{ $flag='section-storm'; &dbg("Storm-control section\n") }
			elsif(($flag eq 'section-storm')&&($z=~m/^([a-zA-Z\d\/\.-]+)\s+(.*)$/)){ 
				$if=lc($1); $x=$2; $x=~s/\s+/;/g; if($if=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $if=$1.$2;};
				foreach $obj2 (@objhost){
					if(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')&&exists($$obj2{'L1'})&&($obj2->{'L1'} eq $if)){
							$obj2->{'desc'} .= '#storm-control:'.$x; &dbg(Dumper(__LINE__,$obj2))
			}	}	}
			elsif($z=~m/^\s+vlan\s+(\d+).*$/){ $obj=pop @objhost; $obj->{'vlan'} .= $1.','; $dl=__LINE__ }
			elsif($z=~m/^\s+ip access-group (\S+) (in|out).*$/){ $obj=pop @objhost; $obj->{'acl'} .= "$1($2),"; $acl->{"$host:$1"} .= $obj->{'L1'}; $dl=__LINE__ }
			elsif($z=~m/^\s+service-policy (input|output) (\S+).*$/){ $obj=pop @objhost; $obj->{'acl'} .= "$2($1),"; $acl->{"$host:pol:$2"} .= $obj->{'L1'}; $dl=__LINE__ }
			elsif($z=~m/^system qos$/){ $flag='ignore-section'  }
			elsif(($flag ne 'ignore-section')&&($z=~m/^\s+service-policy type (network-qos) (\S+).*$/)){ $obj=pop @objhost; $obj->{'acl'} .= "$2($1),"; $acl->{"$host:$2"} .= $obj->{'L1'}; $dl=__LINE__ }
			elsif(($flag ne 'ignore-section')&&($z=~m/^\s+service-policy type (qos|queuing) (input|output) (\S+).*$/)){ $obj=pop @objhost; $obj->{'acl'} .= "$3($1:$2),"; $acl->{"$host:$3"} .= $obj->{'L1'}; $dl=__LINE__ }
			elsif($z=~m/^access-group (\S+) (in|out) interface (\S+).*$/){													# Interface ACL association
				foreach $obj2 (@objhost){
					if(exists($$obj2{'L1'})&&($obj2->{'L1'} eq $3)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){
						$obj2->{'acl'} .= "$1($2),"; $acl->{$obj2->{'origin'}.":$1"} .= $obj2->{'L1'}; &dbg(Dumper(__LINE__,$obj2))
			}	}	}
			elsif(($z=~m/^.*match access-group name (\S+).*$/)||($z=~m/^.*access-group (\S+).*$/)||($z=~m/^.*access-class (\S+).*$/)||($z=~m/^snmp-server community .* R[OW] (\S+).*$/)||($z=~m/^\s+match ip address (\S+).*$/)){ # other ACL uses
				$acl->{"$host:$1"}='' }
			elsif($z=~m/^\s+ip nat (inside|outside|global).*$/){ if(exists($$nat{"$host:$1"})){ $nat->{"$host:$1"} .= ','.$ifs }else{ $nat->{"$host:$1"}=$ifs } &dbg(Dumper(__LINE__,$nat)) }	# Interface NAT association
			elsif($z=~m/^Interface\s+IP-Address\s+OK\?\s+Method\s+Status\s+Protocol.*$/){ $flag='section-if-status'; &dbg("Interface status section\n") }
			elsif(($flag eq'section-if-status')&&($z=~m/^([a-zA-Z\d\/\.-]+)\s+\S+\s+\S+\s+\S+\s+(up|down|administratively)( down)?\s+(up|down)\s*$/)){		# Interface status section
				if($2 eq 'administratively'){ $status='disabled' }else{ $status=$4 }
				foreach $obj2 (@objhost){
					if(($obj2->{'name'} eq lc($1))&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){ $obj2->{'status'}=$status; &dbg(Dumper(__LINE__,$obj2)) }
			}	}
			elsif($z=~m/^([a-zA-Z\d\/\.-]+)\s+\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s+(protocol-\w+\/link-\w+\/admin-\w+).*$/){			# Nexus format case			
				$if=lc($1); $status=$2; if($status=~m/admin-down/){ $status='disabled' }elsif($status=~m/protocol-up/){ $status='up' }else{ $status='down' }
				foreach $obj2 (@objhost){
					if(($obj2->{'name'} eq $if)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){ $obj2->{'status'}=$status; &dbg(Dumper(__LINE__,$obj2)) }
			}	}
			elsif($z=~m/^([a-z]{2,4}[\d\/\.]+)\s+.*\s(connected|disabled|notconnect|notconnec|sfpInvali|sfpAbsent|err-disabled)\s+\S+\s+(\S+)\s+(\S+)\s+.*$/i) {
				$status="$2|$3|$4"; $if=lc($1); if($if=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $if=$1.$2;}; 
				foreach $obj2 (@objhost){
					if(exists($$obj2{'L1'})&&($obj2->{'L1'} eq $if)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){ $obj2->{'status'}=$status; &dbg(Dumper(__LINE__,$obj2)) }
			}	}
			elsif($z=~m/^failover interface ip (\S+) (\S+) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) standby (\S+).*$/){			# Failover section
				foreach $obj2 (@objhost){
					if(($obj2->{'name'} eq $1)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){
						$obj2->{'L3'} .= $2.'/'.unpack('%32b*',inet_aton($3)).','.$4.'/'.unpack('%32b*',inet_aton($3)).','; &dbg(Dumper(__LINE__,$obj2))
			}	}	}
			elsif($z=~m/^VLAN\s+Name\s+Status\s+Ports.*$/){ $flag='section-vlan-status'; &dbg("Vlan status section\n") }			# Vlan section start
			elsif(($flag eq 'section-vlan-status')&&($z=~m/^(\d+)\s+(\S+)\s+active\s*$/)){ %{$obj}=('origin'=>$host,'name'=>$2,'class'=>'vlan','vlan'=>$1,'L1'=>'','desc'=>''); $dl=__LINE__ }
			elsif(($flag eq 'section-vlan-status')&&($z=~m/^(\d+)\s+(\S+)\s+active\s+([a-zA-Z\d\/\.\,\-\s]+)$/)){
				%{$obj}=('origin'=>$host,'name'=>$2,'class'=>'vlan','vlan'=>$1,'L1'=>lc($3).',','desc'=>''); $obj->{'L1'}=~s/,\s/,/g; $obj->{'L1'}=~s/eth/et/g; $dl=__LINE__ }
			elsif(($flag eq 'section-vlan-status')&&($z=~m/^\s+([a-zA-Z\d\/\.\,\-\s]+)$/)){ $obj=pop @objhost; $obj->{'L1'} .= lc($1).','; $obj->{'L1'}=~s/,\s/,/g; $obj->{'L1'}=~s/eth/et/g; $dl=__LINE__ }
			elsif(($flag eq 'section-vlan-status')&&($z=~m/^\-+$/)){ $flag='' }
			elsif($z=~m/^id\s+Port\s+Status( Consistency Reason)?\s+Active vlans\s*$/){ $flag='section-vpc-status'; $map=''; &dbg("vPC status section\n") }	# vPC (peer-link) section start (Nexus)
			elsif(($flag eq 'section-vpc-status')&&($z=~m/^\s+([\d\,\-\s\.]+)\s*$/)){ $map .= $1; &dbg("vPC vlan list $map\n") }
			elsif(($flag eq 'section-vpc-status')&&(($z=~m/^\d+\s+(\S+)\s+up\s+success\s+success\s+(\S+)\s*$/)||($z=~m/^\d+\s+(\S+)\s+up\s+(\S+)\s*$/))){ 	# changing interface
				$n1=$1; $n2=$2; &dbg(Dumper(__LINE__,"Section vpc status $n1,$n2"));
				if(($map!~m/^[\s-]*$/)&&($map!~m/\.{3}$/)){ 																				# if incomplete list, better not document
					$p=$map; $p=~s/\s//g; $map=&parse($p); if($if=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $if=$1.$2;};
					foreach $obj2 (@objhost){
						if(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'vlan')&&($obj2->{'L1'}!~m/$if,/)){ $x=$obj2->{'vlan'}; if($map=~m/\b$x\b/){ $obj2->{'L1'} .= $if.','; &dbg(Dumper(__LINE__,$obj2)) } }
						elsif(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')&&($obj2->{'L1'} eq $if)){ $obj2->{'vlan'}=$map; &dbg(Dumper(__LINE__,$obj2)) }
				}	}
				$if=lc($n1); $map=$n2
			}
			elsif(($flag eq 'section-vpc-status')&&(($z=~m/^.*#.*$/)||($z=~m/^\s*$/))){ 																# end of section
				&dbg(Dumper(__LINE__,"End of section vpc status"));
				if(($map!~m/^\s*$/)&&($map!~m/\.{3}$/)){ 																				# if incomplete list, better not document
					$p=$map; $p=~s/\s//g; $map=&parse($p); if($if=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $if=$1.$2;};
					foreach $obj2 (@objhost){
						if(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'vlan')&&($obj2->{'L1'}!~m/$if,/)){ $x=$obj2->{'vlan'}; if($map=~m/\b$x\b/){ $obj2->{'L1'} .= $if.','; &dbg(Dumper(__LINE__,$obj2)) } }
						elsif(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')&&($obj2->{'L1'} eq $if)){ $obj2->{'vlan'}=$map; &dbg(Dumper(__LINE__,$obj2)) }
				}	}
				$flag=''
			}
			elsif(($z=~m/Port\s+Vlans in spanning tree forwarding state and not pruned$/)||($z=~m/Port\s+STP Forwarding$/)){ $flag='section-vlan-map'; &dbg("Vlan/Interface map section\n") }
			elsif(($flag eq 'section-vlan-map')&&($z=~m/^([a-z]{2,4}[\d\/\.\-]+)\s+([\d\,\-]+)$/i)){
				$if=lc($1); $map=&parse($2);	if($if=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $if=$1.$2;};
				foreach $obj2 (@objhost){
					if(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'vlan')){ $x=$obj2->{'vlan'}; if($map=~m/\b$x\b/&&$obj2->{'L1'}!~m/$if,/){ $obj2->{'L1'} .= $if.','; &dbg(Dumper(__LINE__,$obj2)) } }
					elsif(($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')&&($obj2->{'L1'} eq $if)){ $obj2->{'vlan'}=$map; &dbg(Dumper(__LINE__,$obj2)) }
			}	}
			elsif(($flag=~m/^section-.*$/)&&($z=~m/^.*#.*$/)){ $flag=''; &dbg("Mark Section end\n") }						# Section end
			elsif(($sw!~m/$host;/)&&($z=~m/^\s+Bridge Identifier has priority .*, address\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})$/)){			# STP section
				%{$obj}=('origin'=>$host,'name'=>"Bridge ID",'class'=>'stp','L1'=>'self','L2'=>$1,'desc'=>$z); $map=$1; $dl=__LINE__ ; $sw .= "$host;"}		# Self IP
			elsif($z=~m/^\s+(Port \d+) \(([a-zA-Z]{2})[a-zA-Z-]+([\d\/\.-]+).*\) of (\S+) is (.*)$/){
				%{$obj}=('origin'=>$host,'name'=>$1,'class'=>'stp','L1'=>lc("$2$3"),'vlan'=>$4,'action'=>$5,'desc'=>$z); $more=lc("$2$3"); $dl=__LINE__ }
			elsif($z=~m/^\s+Port path cost\s+\d+, Port priority\s+\d+, Port Identifier\s+(\d+\.\d+)\.?$/){ $obj=pop @objhost; $obj->{'name'} .= "($1)"; $switch->{lc("$host:$1")}=$more; $dl=__LINE__ }
			elsif($z=~m/^\s+Designated bridge has priority \d+, address\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})$/){
				if(($map ne $1)&&($1 ne '0000.0000.0000')){ $obj=pop @objhost; $obj->{'L2'}=$1; $obj->{'status'}='Sw-Sw'; $dl=__LINE__; $flag='Sw-Sw' }else{ $flag='' } }
			elsif($z=~m/^\s+Designated port id is (\d+\.\d+), designated path cost \d+$/){ if($flag eq 'Sw-Sw'){ $obj=pop @objhost; if($map ne $obj->{'L2'}){ $obj->{'L1'} .= " > $1"}; $dl=__LINE__ } }
			elsif($z=~m/^.*access-list (\S+) remark (.*)$/){ $name=$1; if($map ne $name){ $n=1; $nacl=0; $map=$name }; $desc .= '# '.$2; $nacl++; &dbg("Remark: $desc $nacl\n"); }		# ACL section
			elsif($z=~m/^\s+remark (.*)$/){ $desc .= '# '.$1; $nacl++; &dbg("Remark: $desc $nacl\n"); }
			elsif(($z=~m/^access-list .*$/)||($z=~m/^ip access-list .*/)||($z=~m/^\s+(permit|deny).*$/)||($z=~m/^\s+\d+ (permit|deny).*$/)){
				$prot='';
				if($z=~m/^access-list (\S+) (standard|extended) (permit|deny)\s+(\S+)(.*)$/){ $name=$1;$action=$3;$prot=$4;$more=$5;$flag_more=1 }
				elsif($z=~m/^access-list (\d{1,2}|1[3-9]\d{2}) (permit|deny)(.*)$/){ $name=$1;$action=$2;$prot='ip';$more=$3;$flag_more=1 }
				elsif($z=~m/^access-list (1\d{2}|2[0-6]\d{2}) (permit|deny)\s+(\S+)(.*)$/){ $name=$1;$action=$2;$prot=$3;$more=$4;$flag_more=1 }
				elsif($z=~m/^ip access-list (standard|extended) (\S+)(.*)$/){ $n=1; $nacl=0; $name=$2; $map=$name; &dbg("ACL: $name\n") }
				elsif($z=~m/^ip access-list (\S+)(.*)$/){ $n=1; $nacl=0; $name=$1; $map=$name; &dbg("ACL: $name\n") }
				elsif($z=~m/^\s+(permit|deny)\s+(\S+)(.*)$/){ $action=$1;$prot=$2;$more=$3;$flag_more=1 }
				elsif($z=~m/^\s+\d+ (permit|deny)\s+(\S+)(.*)$/){ $action=$1;$prot=$2;$more=$3;$flag_more=1 }
				if($flag_more){
					if($map ne $name){ $n=1; $nacl=0; $map=$name }
					$nacl++; $desc .= '#line '.$nacl;
					if(($prot eq 'object-group')&&($more=~m/\s+(\S+)(.*)$/)){ $prot=$1; $more=$2 }							# case of object-group for protocol
					if($prot){ $p="$prot:" }else{ $p='' }
					%{$obj}=('origin'=>$host,'name'=>"$name line $n",'class'=>'acl','action'=>$action,'vpn'=>'Any'); $dl=__LINE__;
					if(($more=~m/^\s+object-group (\S+)(.*)$/)||($more=~m/^\s+object (\S+)(.*)$/)||($more=~m/^\s+(any)(.*)$/)||($more=~m/^\s+host (\S+)(.*)$/)){ $obj->{'L3'}=$1; $more=$2 } # SRC subpart
					elsif($more=~m/^\s+(\S+)\s+(\d{1,3})(\.\d{1,3}\.\d{1,3}\.)(\d{1,3})(.*)$/){
						if($2 ge $4){ $x=unpack('%32b*',inet_aton("$2$3$4")) }else{ $x=32-unpack('%32b*',inet_aton("$2$3$4")) }		# Convert wildcard or netmask to prefix
						$obj->{'L3'}="$1/$x"; $more=$5 }
					elsif($more=~m/^\s+(\S+)(.*)$/){ $obj->{'L3'}=$1; $more=$2 }
					else{ &dbg("*NOT-MATCHED* $z\n") }
					if($more=~m/^\s+(eq|neq|lt|gt)\s+(\S+)(.*)$/){ $obj->{'L4'}="$p$1#$2"; $more=$3 }						# SRC-SRVC subpart
					elsif($more=~m/^\s+(range)\s+(\S+)\s+(\S+)(.*)$/){ $obj->{'L4'}="$p$1#$2-$3"; $more=$4 }
					elsif(($more=~m/^\s+object-group (\S+)(.*)$/)||($more=~m/^\s+object (\S+)(.*)$/)){
						foreach $obj2 (@objhost){																			# check if object-group is a SRVC or DST subpart
							if(($obj2->{'class'} eq 'ASA-service')&&($obj2->{'name'} eq $1)&&($obj2->{'origin'} eq $host)){ $obj->{'L4'}='&src:'.$p.$1; $more=$2 }
					}	}
					$flag='';																								# DST subpart
					if(($more=~m/^\s+object-group (\S+)(.*)$/)||($more=~m/^\s+object (\S+)(.*)$/)||($more=~m/^\s+interface (\S+)(.*)$/)||($more=~m/^\s+(any)(.*)$/)||($more=~m/^\s+host (\S+)(.*)$/)){
						$obj->{'L3'} .= ' -> '.$1; $more=$2; $flag='dst' }
					elsif($more=~m/^\s+(\S+)\s+(\d{1,3})(\.\d{1,3}\.\d{1,3}\.)(\d{1,3})(.*)$/){
						if($2 ge $4){ $x=unpack('%32b*',inet_aton("$2$3$4")) }else{ $x=32-unpack('%32b*',inet_aton("$2$3$4")) }		# Convert wildcard or netmask to prefix
						$obj->{'L3'} .= " -> $1/$x"; $more=$5; $flag='dst' }
					elsif($more=~m/^\s+(\S+)(.*)$/){ $obj->{'L3'} .= " -> $1"; $more=$2 }
					$srvc='';																								# DST-SRVC subpart
					if($more=~m/^\s+(eq|neq|lt|gt)\s+(\S+)(.*)$/){ $srvc="$p$1#$2"; $more=$3 }
					elsif($more=~m/^\s+(range)\s+(\S+)\s+(\S+)(.*)$/){ $srvc="$p$1#$2-$3"; $more=$4 }
					elsif(($more=~m/^\s+object-group (\S+)(.*)$/)||($more=~m/^\s+object (\S+)(.*)$/)){ $srvc="$p$1"; $more=$2 }
					elsif($more=~m/^\s+(\S+)(.*)$/){ $n1=$1; $n2=$2;
						if($n1 !~ m/^(inactive|log|log-input|time-range|dscp|tos|option|precedence)$/){ $srvc="$p$n1"; $more=$n2 } }
					if(exists($$obj{'L4'})){																				# Build L4 data
						if($srvc){ $obj->{'L4'}=$srvc.$obj->{'L4'} } }														# specific src and dst port
					elsif($srvc){ $obj->{'L4'}="$srvc" }																	# any to dst port
					elsif($prot){ $obj->{'L4'}="$prot" }																	# only protocol
					$p=''; while($more=~m/^\s+(dscp|tos|option|precedence)\s+(\S+)(.*)$/){ $p.="&$1_$2"; $more=$3 }; $obj->{'L4'} .= $p;
					if($more=~m/.*inactive.*/){ $obj->{'status'}='off' }else{ $obj->{'status'}='on' }; $obj->{'desc'}=$desc; $desc=''; $flag_more=0;	# ACL status, description
					$n++;
			}	}
			elsif($z=~m/^crypto map (\S+ \d+) ipsec.*$/){ $vpn=$1 }
			elsif(($z=~m/^crypto map (\S+ \d+) match address (\S+).*$/)||($z=~m/^\s+match address (\S+).*/)){				# ACL to VPN match
				if(defined $2){ $vpn=$1;$name=$2 }else{ $name=$1 }
				$acl->{"$host:$name"}='';
				foreach $obj2 (@objhost){
					if(($obj2->{'name'}=~m/^$name line.*/)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'acl')){ $obj2->{'vpn'}=$vpn; &dbg(Dumper(__LINE__,$obj2)) }
			}	}
			elsif($z=~m/^crypto map (\S+ \d+) set peer (\S+).*$/){ %{$obj}=('origin'=>$host,'name'=>$1,  'class'=>'crypto-peer','L3'=>$2.'/32'); $dl=__LINE__ } # Crypto peer object
			elsif($z=~m/^\s+set peer (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){ %{$obj}=('origin'=>$host,'name'=>$vpn,'class'=>'crypto-peer','L3'=>$1.'/32'); $dl=__LINE__ }
			elsif($z=~m/^ip nat\s+\S+\s+(source|destination)\s+(static|list)\s+(.*)$/){										# NAT section
				$if='';																										# Check NAT enabled interfaces
				if(exists($$nat{"$host:enable"})){  $if=$nat->{"$host:enable"} }; 
				if(exists($$nat{"$host:inside"})){  $if .= $nat->{"$host:inside"} }
				if(exists($$nat{"$host:outside"})){ $if .= '>>'.$nat->{"$host:outside"} }
				if(($2 eq 'static')&&($3=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)$/)){
					%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>"$1/32 <> $2/32",'status'=>'on','desc'=>$z); $dl=__LINE__; $count++;
					if($3=~/^\s+route-map (\S+).*$/){ $obj->{'acl'}='Route-map:'.$1 } }
				elsif(($2 eq 'static')&&($3=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+interface\s+(\S+)(.*)$/)){
					$ip=$1; $if2=$2;
					foreach $obj2 (@objhost){
						if(($obj2->{'name'} eq $if2)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')&&($obj2->{'status'} ne m/|secIP|/)){
							%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>"$ip/32 <> ".$obj2->{'L3'},'status'=>'on','desc'=>$z); $dl=__LINE__; $count++;
							if($3=~/^\s+route-map (\S+).*$/){ $obj->{'acl'}='Route-map:'.$1 }
				}	}	}
				elsif(($2 eq "static")&&($3=~m/^(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d*)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d*)(.*)$/)){
					%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>"$2/32 <> $4/32",'L4'=>"$1:$3 <> $1:$5",'status'=>'on','desc'=>$z); $dl=__LINE__; $count++;
					if($6=~/^\s+route-map (\S+).*$/){ $obj->{'acl'}='Route-map:'.$1 } }
				elsif(($2 eq 'list')&&($3=~/^(\S+) (interface|pool) (\S+)(.*)$/)){
					($2 eq 'pool')?$dst=$3.'(pool)':$dst=$3; $name=$1; $more=$4;
					foreach $obj2 (@objhost){
						if(($obj2->{'name'}=~m/^$name line.*/)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'acl')){
							%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$obj2->{'L3'}." <> $dst",'status'=>'on','desc'=>$z); $dl=__LINE__; $count++;
							&dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost, $obj; $obj={}
					}	}
					if($more=~/^\s+route-map (\S+).*$/){ $obj->{'acl'}='Route-map:'.$1 } }
				else{ &dbg("*NOT-MATCHED* $z\n") } }
			elsif($z=~m/^static \((.*)\)\s+(.*)$/){																			# ASA/PIX case for NAT
				$if=lc($1);
				if($2=~m/^(tcp|udp)\s+(\S+)\s+(\S+)\s+access-list\s+(\S+).*$/){
					$ip=$2;	$srvc="$1#$3"; $name=$4; $acl->{"$host:$4"}='';
					foreach $obj2 (@objhost){
						if(($obj2->{'name'}=~m/^$name line.*/)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'acl')){
							%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$obj2->{'L3'}.' <> '.$ip,'L4'=>$obj2->{'L4'},'status'=>'on','desc'=>$z);
							$obj->{'L3'}=~s/ -> any//;; $obj->{'L4'}=~s/ -> .*//; $obj->{'L4'}.=' <> '.$srvc; $dl=__LINE__; $count++; 
							&dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost, $obj; $obj={}
				}	}	}
				elsif($2=~m/^(\S+)\s+access-list\s+(\S+).*$/){
					$ip=$1; $name=$2; $acl->{"$host:$2"}='';
					foreach $obj2 (@objhost){
						if(($obj2->{'name'}=~m/^$name line.*/)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'acl')){
							%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$obj2->{'L3'}.' <> '.$ip,'status'=>'on','desc'=>$z); $dl=__LINE__; $count++;
							&dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost, $obj; $obj={}
				}	}	}
				elsif($2=~m/^(\S+)\s+(\S+)(.*)$/){
					$src=$2; $src_t=$1;	if($3=~m/^\s+netmask (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){ $n=unpack('%32b*',inet_aton($1)) }else{ $n=&class($src) }
					%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$src.'/'.$n.' <> '.$src_t.'/'.$n,'status'=>'on','desc'=>$z); $dl=__LINE__; $count++; }
				else{ &dbg("*NOT-MATCHED* $z\n") } }
			elsif($z=~m/^\s+nat \((.*)\)\s+(static|dynamic)\s+(\S+)(.*)$/){													# ASA 8.3+ case for NAT
				if(!$expand&&($flag_obj eq 'obj-def')){ pop @objhost }														# Object definition already exists, so removing last empty definition
				$if=lc($1); $src=$name; $src_t=$3; if($4=~m/\s+inactive\s+/){ $status='off'}else{ $status='on' }; if($src_t=~m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){ $src_t.='/32' };
					%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$src.' <> '.$src_t,'status'=>$status,'desc'=>$z); $dl=__LINE__; $count++ }
			elsif($z=~m/^\s*nat \((.*)\)\s+source\s+(static|dynamic)\s+(\S+)\s+(\S+)(.*)$/){
				$if=lc($1); $src=$3; $src_t=$4; $more=$5; $flag='';
				if($more=~m/^\s+destination\s+(static|dynamic)\s+(\S+)\s+(\S+)(.*)$/){ $dst=$2; $dst_t=$3; $more=$4; $flag='with-dst ' }; 
				if($more=~m/^\s+service\s+(\S+)\s+(\S+)(.*)$/){ $srvc=$1; $srvc_t=$2; $more=$3; $flag.='with-srvc ' }; 
				if($more=~m/\s+inactive\s+/){ $status='off'}else{ $status='on' };
				%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'status'=>$status,'desc'=>$z); $dl=__LINE__; $count++;
				if($flag=~m/with-dst/){ $obj->{'L3'}=$src.' -> '.$dst.' <> '.$src_t.' -> '.$dst_t }else{ $obj->{'L3'}=$src.' <> '.$src_t } 
				if($flag=~m/with-srvc/){ $obj->{'L4'}= $srvc.' <> '.$srvc_t } }
			elsif($z=~m/^global \((.*)\)\s+(\d+)\s+(.*)$/){
				$if2=$1; $map=$2;
				if(($3=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)$/)
				 ||($3=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)$/)){
					$ip=$1;	if($2=~m/\s+netmask\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*/){ $nat->{"$host:$map"}=$ip.'/'.unpack('%32b*',inet_aton($1)) }else{ $nat->{"$host:$map"}=$ip.'/'.&class($ip) }
					&dbg("NAT global: ".$nat->{"$host:$map"}."\n") }
				else{ &dbg("*NOT-MATCHED* $z\n") } }
			elsif($z=~m/^nat \((.*)\)\s+(\d+)\s+(.*)$/){
				$if=lc($1); $map=$2;
				if($3=~m/^(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*(inside|outside).*$/){
					$ip=$1.'/'.unpack('%32b*',inet_aton($2));
					if(defined $3){ if($3 eq 'inside'){ $if="$if << $if2" }elsif($3 eq 'outside'){ $if="$if >> $if2" }else{ $if="$if,$if2" } }else{ $if="$if,$if2" }
					%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$ip.' <> '.$nat->{"$host:$map"},'status'=>'on'); $count++; }
				elsif($3=~m/^access-list\s+(\S+)(.*)$/){
					$name=$1; $acl->{"$host:$1"}='';
					if(defined $2){ if($2 eq 'inside'){ $if="$if << $if2" }elsif($2 eq 'outside'){ $if="$if >> $if2" }else{ $if="$if,$if2" } }else{ $if="$if,$if2" }
					foreach $obj2 (@objhost){
						if(($obj2->{'name'}=~m/^$name line.*/)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'acl')){
							%{$obj}=('origin'=>$host,'name'=>"NAT line $count",'class'=>'nat','L1'=>$if,'L3'=>$obj2->{'L3'}.' <> '.$nat->{"$host:$map"},'status'=>'on','desc'=>$z); $dl=__LINE__;
							if(exists($$obj2{'L4'})){ $obj->{'L4'}=$obj2->{'L4'}.' <> '.$obj2->{'L4'} }; $count++;
							&dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost, $obj; $obj={}
				}	}	}
				else{ &dbg("*NOT-MATCHED* $z\n") } }
			elsif($z=~m/^\s+ip nat pool (\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\S+\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){	# NAT object section
				%{$obj}=('origin'=>$host,'name'=>$1,'class'=>'natpool','L3'=>'('.$2.'-'.$3.')/'.unpack('%32b*',inet_aton($4))); $dl=__LINE__ }
			elsif($z=~m/^\s+\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d+) is (variably )?subnetted.*$/){ $n=$1; &dbg("Route prefix: $1\n") }			# Route section
			elsif($z=~m/^\S\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*$/){ 
				$ip=$1.'/'.unpack('%32b*',inet_aton($2)); $n=$z; &dbg("Route: (inc) $1\/$2 -> ...\n") }
			elsif($z=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d+), ubest\/mbest:.*$/){	$ip=$1; $n=$z; &dbg("Route: (inc) $ip -> ...\n") }
			elsif(($z=~m/^\s+\S+\s+via\s+(\S+), (\S+).*$/)
				||($z=~m/^\s+is directly (connected), (\S+).*$/)){
				%{$obj}=('origin'=>$host,'name'=>"$ip",'class'=>'route','L1'=>lc($2),'L3'=>"$ip => $1",'desc'=>$n.$z); $dl=__LINE__ }			
			elsif($z=~m/^\s+\*+via (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}), (\S+), \[[\d\/]+\], \S+, (\S+)\s*$/){
				if(($3 eq 'direct')||($3 eq 'local')||($3 eq 'hsrp')||($3 eq 'vrrp')||($3 eq 'glbp')){ $x='connected' }else{ $x=$1 }; 
				%{$obj}=('origin'=>$host,'name'=>"$ip",'class'=>'route','L1'=>lc($2),'L3'=>"$ip => $x",'desc'=>$n.$z); $dl=__LINE__ }			
			elsif($z=~m/^\s+\*+via (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}), \[[\d\/]+\], \S+, (\S+)\s*$/){
				if(($2 eq 'direct')||($2 eq 'local')||($2 eq 'hsrp')||($2 eq 'vrrp')||($2 eq 'glbp')){ $x='connected' }else{ $x=$1 }; 
				%{$obj}=('origin'=>$host,'name'=>"$ip",'class'=>'route','L3'=>"$ip => $x",'desc'=>$n.$z); $dl=__LINE__ }			
			elsif(($z=~m/^\S\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) is directly (connected), (\S+).*$/)
				||($z=~m/^\S\*?\s+(\S+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\S+\s+via\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}), (\S+).*$/)){
				$ip=$1.'/'.unpack('%32b*',inet_aton($2)); %{$obj}=('origin'=>$host,'name'=>"$ip",'class'=>'route','L1'=>lc($4),'L3'=>"$ip => $3",'desc'=>$z); $dl=__LINE__ }
			elsif(($z=~m/^\S\s+(\S{2}\s+)?(\S+) is directly (connected).*$/)
				||($z=~m/^\S\*?\s+(\S{2}\s+)?(\S+)\s+\S+\s+via\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)){
				$ip=$3;	$name=$2;
				if($2=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/){ $name=$1.$n; &dbg("Route prefix add: $name\n") }
				%{$obj}=('origin'=>$host,'name'=>"$name",'class'=>'route','L3'=>"$name => $ip",'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^\S\*?\s+(\S+)\s+\S+\s+via\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){
				$ip=$2;	$name=$1;
				if($1=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/){ $name=$1.$n; &dbg("Route prefix add: $name\n") }
				%{$obj}=('origin'=>$host,'name'=>"$name",'class'=>'route','L3'=>"$name => $ip",'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^\s+\S+\s+via\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){
				$obj2=pop @objhost; $ip=$obj2->{'name'}; push @objhost,$obj2;
				%{$obj}=('origin'=>$host,'name'=>"$ip",'class'=>'route','L3'=>"$ip => $1",'desc'=>$z); $dl=__LINE__ }
			elsif(($z=~m/^\s*(\d+)\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+(dynamic|static)\s*\S*\s+([a-zA-Z\d\/\.-]+)\s*$/i)						# ARP-MAC section (Vlan/MAC/Type/Interface)
				||($z=~m/^[\s\*\+]*(\d+)\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+(dynamic|static)\s+\S+\s+\S+\s+([a-zA-Z\d\/\.\,-]+)\s*$/i)		# (Vlan/MAC/Type/Interface - Other)
				||($z=~m/^[\s\*\+]*(\d+)\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+(dynamic|static)\s+\S+\s+\-\s*$/i)								# (Vlan/MAC/Type/Interface - Static)
				||($z=~m/^[\s\*\+]*(\d+)\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+(dynamic|static)\s+\d+\s+\S\s+\S\s+([a-zA-Z\d\/\.-]+)\s*$/i)		# (Vlan/MAC/Type/Interface - Nexus)
				||($z=~m/^([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+(Self|Dynamic)\s+(\d+)\s+([a-zA-Z\d\/\.-]+)\s*$/i)){								# (MAC/Type/Vlan/Interface)
				$n1=$1;$n2=$2;$n3=$3; if($4){ $n4=$4}else{ $n4='Switch' }; if($n3=~m/static/i){ $n4='Switch'};
				if($n1=~m/^[a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4}$/){ %{$obj}=('origin'=>$host,'name'=>$n1,'class'=>'arp','L2'=>$n1,'L1'=>lc($n4),'vlan'=>$n3,'desc'=>$z); $dl=__LINE__ }
				else{ %{$obj}=('origin'=>$host,'name'=>$n2,'class'=>'arp','L2'=>$n2,'L1'=>lc($n4),'vlan'=>$n1,'desc'=>$z); $dl=__LINE__ }
				if($obj->{'L1'}=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $obj->{'L1'}=$1.$2;};									# Convert interface name to short name
				if($obj->{'L1'}=~m/(Router|Switch|Stby-Switch|CPU|Self)/i){													# Associate MAC with VTI
					$obj->{'L1'}='vl'.$obj->{'vlan'};
					foreach $obj2 (@objhost){
						if(exists($$obj2{'vlan'})&&($obj2->{'vlan'} eq $obj->{'vlan'})&&($obj2->{'L1'} eq $obj->{'L1'})&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){
							$obj2->{'L2'} .= $obj->{'L2'}.','; &dbg(Dumper(__LINE__,$obj2)) 
			} 	} 	} 	}
			elsif($z=~m/^\s*\S+\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+[\d-]+\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+\S+\s+([a-zA-Z\d\/\.-]+)$/){ # IP/MAC/Interface
				%{$obj}=('origin'=>$host,'name'=>$2,'class'=>'arp','L2'=>$2,'L1'=>lc($3),'L3'=>$1,'desc'=>$z); $dl=__LINE__;
				if($obj->{'L1'}=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $obj->{'L1'}=$1.$2;};									# Convert interface name to short name
				if($obj->{'L1'}=~/vl(\d+)/){ $obj->{'vlan'}=$1 } }
			elsif($z=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+[\d\:-]+\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})\s+([a-zA-Z\d\/\.-]+)[\s\*]*$/){ # IP/MAC/Interface (Nexus)
				%{$obj}=('origin'=>$host,'name'=>$2,'class'=>'arp','L2'=>$2,'L1'=>lc($3),'L3'=>$1,'desc'=>$z); $dl=__LINE__;
				if($obj->{'L1'}=~m/^([a-z]{2})[a-z-]+([\d\/\.-]+)/){ $obj->{'L1'}=$1.$2;};									# Convert interface name to short name
				if($obj->{'L1'}=~/vl(\d+)/){ $obj->{'vlan'}=$1 } }
			elsif($z=~m/^\s+(\S+)\s+(\S+)\s+([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4}).*$/){										# Interface/IP/MAC (ASA)
				%{$obj}=('origin'=>$host,'name'=>$3,'class'=>'arp','L2'=>$3,'L1'=>lc($1),'L3'=>$2,'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/(^|.*?\s+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d{0,2})(\s+.*|$)/){ 								# IP in unknown class
				$n=$2; 
				if($n ne '255.255.255.255'){
					%{$obj2}=('origin'=>$host,'name'=>$n,'class'=>'unknown/ignored','L3'=>$n,'desc'=>$z); $dl=__LINE__;
					&dbg(Dumper("Matched at script line:$dl (ignored)",$obj2)); if($Ignored){ push @objunk,$obj2 } $obj2={} 
			}	}
			elsif($z=~m/(^|.*?\s+)([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})(\s+.*|$)/i){											# MAC in unknown class
				$n=$2;
				if($n!~m/^ffff\.ffff\.ffff$/i){
					%{$obj2}=('origin'=>$host,'name'=>$n,'class'=>'unknown/ignored','L2'=>$n,'desc'=>$z); $dl=__LINE__;
					&dbg(Dumper("Matched at script line:$dl (ignored)",$obj2)); if($Ignored){ push @objunk,$obj2 } $obj2={} 
			}	}
			if(scalar %{$obj}){ &dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost,$obj; $obj={}; $dl=0 } # If object not empty, add to list
		}close FILE;}
		elsif($ext=~/alteon/){																								# Parse Nortel/Alteon switch/loadbalancers
		open FILE, "< $file" or warn "Can't open $file : $!";
		while (<FILE>){
			$z=$_;
			chomp $z; &dbg("line:$z\n");
			if(($flag eq 'hostname')&&($z=~m/^\/\*\s+(\S+)\s*$/)){ $host=$1; $flag=''; $obj=pop @objhost; $obj->{'origin'}=$host; $dl=__LINE__ }		# Hostname section
			elsif($z=~m/^script start.*$/){ $flag='hostname' }		
			elsif($z=~m/^\/c\/sys\/(radius|syslog|ntp).*$/){ %{$obj}=('origin'=>$host,'name'=>"$1 ",'class'=>'mngmt','desc'=>$z); $dl=__LINE__ }		# Management IPs
			elsif($z=~m/^\s+(prisrv|secsrv|host) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){ $obj=pop @objhost; $obj->{'name'}.=$1; $obj->{'L3'}=$2; $obj->{'desc'}.=" $z"; $dl=__LINE__ }
			elsif($z=~m/^$/){ $flag='' }																					# section end
			elsif($z=~m/^\/\* Version \S+,  Base MAC address ([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}).*$/i){		# STP section
				$mac=lc("$1$2.$3$4.$5$6"); %{$obj}=('origin'=>$host,'name'=>"Bridge ID",'class'=>'stp','L1'=>'self','L2'=>$mac,'desc'=>$z); $map=$1; $dl=__LINE__ }
			elsif($z=~m/^Spanning Tree Group (\d+): On \(STP\/PVST\)$/){ $aux="MST$1"; &dbg("STP MST: $1\n") }
			elsif($z=~m/^VLANs:\s+(\d[\s\d]*).*$/){ $n=$1; $n=~s/\s+/,/g }
			elsif($z=~m/^\s+\d+\s+([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2})\s+\d+\s+(\S+)\s+.*$/i){
				if($7 ne '(null)'){	%{$obj}=('origin'=>$host,'name'=>$7,'class'=>'stp','vlan'=>$aux,'L1'=>lc($7),'L2'=>lc("$1$2.$3$4.$5$6"),'action'=>'root forwarding','Status'=>'Sw-Sw','desc'=>$z); $dl=__LINE__ } }
			elsif($z=~m/^Port  Priority   Cost      State       Designated Bridge     Des Port$/){ $flag='stp-ports' }
			elsif(($flag eq'stp-ports')&&($z=~m/^(\S+)\s+\d+\s+\d+\s+(\S+)\s+.*$/)){ %{$obj}=('origin'=>$host,'name'=>$1,'class'=>'stp','vlan'=>$n,'L1'=>lc($1),'action'=>$2,'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^VLAN\s+Name\s+Status\s+Ports.*$/){ $flag='section-vlan-status'; &dbg("Vlan status section\n") }			# Vlan section start
			elsif(($flag eq 'section-vlan-status')&&($z=~m/^----\s+(-*)\s+.*$/)){ $x=length $1; &dbg("Interface desc size:$x\n") }			# Count description size
			elsif(($flag eq 'section-vlan-status')&&($z=~m/^(\d+)\s+(.{$x})\s+(\S+)\s+(.*)/)){ 
				$aux=$1; $name=$2; $status=$3; $map=''; 
				foreach $n (parse_line(" ",0, $4)){ if($n=~m/([A-Z]+)(\d+)\-[A-Z]+(\d+)/){ for ($x=$2;$x<=$3;$x++){ $map .= "$1$x," } }else{ $map .= "$n," } }; $name=~s/\s*$//g;
				%{$obj}=('origin'=>$host,'name'=>$name,'class'=>'vlan','vlan'=>$aux,'L1'=>lc($map),'status'=>$status,'desc'=>$z); $dl=__LINE__ }
			elsif(($z=~m/^\s*(\d+):\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*,\s+vlan\s+(\d+),\s+(\S+).*$/)
				||($z=~m/^\s*(\d+):\sIP4\s(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*,\s+vlan\s+(\d+),\s+(\S+).*$/)){
				$n=unpack('%32b*',inet_aton($3)); %{$obj}=('origin'=>$host,'name'=>"$2/$n",'class'=>'interface','L1'=>lc($1),'L3'=>"$2/$n",'vlan'=>$4,'status'=>$5,'desc'=>$z); $dl=__LINE__ }
			elsif(($z=~m/^Alias\s+Port\s+Speed\s+Duplex\s+Flow Ctrl\s+Link.*$/)||($z=~m/^Alias\s+Port\s+Tag\s+FAST\s+(Lrn)?\s*(Fld)?\s*PVID\s+NAME\s+VLAN\(s\).*$/i)){ $flag='if-status' }
			elsif(($flag eq'if-status')&&($z=~m/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(yes|no)\s+(yes|no)\s+(up|down|disabled).*$/)){ 
				%{$obj}=('origin'=>$host,'name'=>"Port$2",'class'=>'interface','L1'=>lc($1),'status'=>"$7\|$4\|$3"); $dl=__LINE__ }
			elsif(($flag eq'if-status')&&(($z=~m/^(\S+)\s+\d+\s+\S\s+\S\s+\S\s+\S\s+\d+\s+(\S.{14})\s*(\d[\d\s]*)$/)||($z=~m/^(\S+)\s+\d+\s+\S\s+\S\s+\d+\s+(\S.{14})\s*(\d[\d\s]*)$/))){
				foreach $obj2 (@objhost){
					if(exists($$obj2{'L1'})&&($obj2->{'L1'} eq $1)&&($obj2->{'origin'} eq $host)&&($obj2->{'class'} eq 'interface')){
						$obj2->{'desc'}="# $2"; $obj2->{'vlan'}=$3; $obj2->{'desc'}=~s/\s*$//g; $obj2->{'vlan'}=~s/\s+/,/g; &dbg(Dumper(__LINE__,$obj2))
			}	}	}
			elsif(($z=~m/^\s+MAC address\s+VLAN\s+Port\s+(Trnk|Trunk)?\s*State.*$/)||($z=~m/^\s+IP address\s+Flags\s+MAC address\s+VLAN\s+Port.*$/)){ $flag='arp' }		# ARP section
			elsif(($flag eq'arp')&&($z=~m/^\s+([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2})\s+(\d+)\s+(\d+)\s+FWD\s+.*$/i)){
				%{$obj}=('origin'=>$host,'name'=>lc("$1$2.$3$4.$5$6"),'class'=>'arp','vlan'=>$7,'L1'=>lc($8),'L2'=>lc("$1$2.$3$4.$5$6"),'desc'=>$z); $dl=__LINE__ }
			elsif(($flag eq'arp')&&($z=~m/^\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+[A-Z\s]*([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2})\s+(\d+)\s+(\S+).*$/i)){
				%{$obj}=('origin'=>$host,'name'=>lc("$2$3.$4$5.$6$7"),'class'=>'arp','vlan'=>$8,'L1'=>lc($9),'L2'=>lc("$2$3.$4$5.$6$7"),'L3'=>$1,'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^\s+Destination\s+Mask\s+Gateway\s+Type\s+Tag\s+Metr\s+If.*$/){ $flag='route' }						# Route section
			elsif(($flag eq'route')&&($z=~m/^\*?\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(indirect|direct)\s+.*$/)){
				$n=unpack('%32b*',inet_aton($2)); 
				if($4 eq 'direct'){ $r='connected' }else{ $r=$3 }; %{$obj}=('origin'=>$host,'name'=>"$1/$n",'class'=>'route','L3'=>"$1/$n => $r",'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/(^|.*?\s+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d{0,2})(\s+.*|$)/){ 								# IP in unknown class
				$n=$2; 
				if(($n ne '255.255.255.255')&&($1!~m/Software Version|NOTICE|INFO/)){
					%{$obj2}=('origin'=>$host,'name'=>$n,'class'=>'unknown/ignored','L3'=>$n,'desc'=>$z); $dl=__LINE__;
					&dbg(Dumper("Matched at script line:$dl (ignored)",$obj2)); if($Ignored){ push @objunk,$obj2 } $obj2={} 
			}	}
			elsif($z=~m/(^|.*?\s+)([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})(\s+.*|$)/i){						# MAC in unknown class
				$n=$2;
				if($n!~m/^ffff\.ffff\.ffff$/i){
					%{$obj2}=('origin'=>$host,'name'=>$n,'class'=>'unknown/ignored','L2'=>$n,'desc'=>$z); $dl=__LINE__;
					&dbg(Dumper("Matched at script line:$dl (ignored)",$obj2)); if($Ignored){ push @objunk,$obj2 } $obj2={} 
			}	}
			if(scalar %{$obj}){ &dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost,$obj; $obj={} }		# If object not empty, add to list
		}close FILE;}
		elsif($ext=~/host/||$ext=~/ipso/||$ext=~/splat/||$ext=~/bigip/){															# Parse CHK IPSO or normal Linux host
		open FILE, "< $file" or warn "Can't open $file : $!";
		while(<FILE>){
			$z=$_; chomp $z; &dbg("line:$z\n");
			if($z=~m/^(\S+)\s+Link encap:\S+\s+HWaddr\s+([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}).*$/i){		# IPSO, SPLAT, hosts interfaces section
				%{$obj}=('origin'=>$host,'name'=>$1,'L1'=>lc($1),'L2'=>lc("$2$3.$4$5.$6$7"),'class'=>'interface','status'=>'up','desc'=>"$z "); $dl=__LINE__ }
			elsif($z=~m/^\s+inet addr:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})  Bcast:\S+  Mask:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){
				$obj=pop @objhost; $obj->{'L3'} .= $1.'/'.unpack('%32b*',inet_aton($2)); $obj->{'desc'} .= "$z"; $dl=__LINE__ }
			elsif($z=~m/^(\S+):\s+lname (\S+) flags=(.*)$/){
				%{$obj}=('origin'=>$host,'name'=>$1,'class'=>'interface','L1'=>lc($2),'desc'=>"$z "); $dl=__LINE__; $more=$3;
				if($more=~m/<UP,/){ $obj->{'status'}='up' }else{ $obj->{'status'}='down' };	if($more=~m/vlan-id\s+(\d+)/){ $obj->{'vlan'}=$1 } }
			elsif($z=~m/\s+inet.*\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*)\s+broadcast.*/){
				$obj=pop @objhost; $obj->{'L3'} .= "$1,"; $obj->{'desc'} .= "$z"; $dl=__LINE__ }
			elsif($z=~m/^\s+ether ([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2})\s+speed\s+(\S+)\s+(\S+)\s+duplex.*$/i){
				$obj=pop @objhost; $obj->{'L2'}=lc("$1$2.$3$4.$5$6"); $obj->{'status'} .= "|$7|$8"; $dl=__LINE__ }
			elsif($z=~m/^Interface (\S+).*$/){ %{$obj}=('origin'=>$host,'name'=>"vrrp $1",'L1'=>lc($1),'class'=>'interface','desc'=>"$z "); $dl=__LINE__ }		# VRRP config section
			elsif($z=~m/^\s+State:\s+(\S+).*$/) { $obj=pop @objhost; $obj->{'status'}="$1"; $obj->{'desc'} .= "$z"; $dl=__LINE__ }
			elsif($z=~m/^\s+Number of Addresses:\s+(\d+).*$/){ $flag='vrrp'; }
			elsif($z=~m/^\s+VMAC Mode:\s+\S+\s+VMAC:\s+([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}).*$/i) {
				$obj=pop @objhost; $obj->{'L2'}=lc("$1$2.$3$4.$5$6"); $obj->{'desc'} .= "$z"; $dl=__LINE__ }
			elsif(($z=~m/^\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*)$/)&&($flag eq 'vrrp')) {
				$obj=pop @objhost; foreach $ip (split(/\s+/, $1)){ $obj->{'L3'} .= "$ip," }; $obj->{'desc'} .= "$z"; $dl=__LINE__; $flag='' }
			elsif(($z=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)			# IPSO, SPLAT, hosts route table section
				||($z=~m/^(default)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)){
				if($1 eq 'default'){ $ip="0.0.0.0/0" }else{ $ip=$1.'/'.unpack('%32b*',inet_aton($3)) }
				%{$obj}=('origin'=>$host,'name'=>$ip,'class'=>'route','L3'=>"$ip => $2",'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\*\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/){
				%{$obj}=('origin'=>$host,'name'=>"$1/$2",'class'=>'route','L3'=>$1.'/'.unpack('%32b*',inet_aton($2)).' => connected','desc'=>$z); $dl=__LINE__ }
			elsif(($z=~m/^\S\*?\s+([\d\.]+\/?\d*)\s?\S?\s+via\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/)
				  ||($z=~m/^\S\s+([\d\.]+\/?\d*)\s+is directly (connected).*$/)){
				$ip=$1; $p=$2; 
				if($ip=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d+)$/){ $ip=$1.'.0'.$2 }
				elsif($ip=~m/^(\d{1,3}\.\d{1,3})(\/\d+)$/){ $ip=$1.'.0.0'.$2 }
				elsif($ip=~m/^(\d{1,3})(\/\d+)$/){ $ip=$1.'.0.0.0'.$2 }
				%{$obj}=('origin'=>$host,'name'=>$ip,'class'=>'route','L3'=>"$ip => $p",'desc'=>$z); $dl=__LINE__ }
			elsif($z=~m/^.*\((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\)\s+at\s+([A-F\d]{1,2}):([A-F\d]{1,2}):([A-F\d]{1,2}):([A-F\d]{1,2}):([A-F\d]{1,2}):([A-F\d]{1,2})(.*)$/i){ # IP/MAC/Interface
				$n1=lc(sprintf("%02s%02s.%02s%02s.%02s%02s",$2,$3,$4,$5,$6,$7)); $n2=$8; %{$obj}=('origin'=>$host,'name'=>$n1,'class'=>'arp','L2'=>$n1,'L3'=>$1,'desc'=>$z); $dl=__LINE__;
				if($n2=~m/ on (.*)/){ $n2=$1; if ($1!~m/~/){ $obj->{'L1'}=lc($n2) } } }
			elsif($ext eq 'bigip'){																							# F5 bigip section
				if($z=~m/^vlan (\S+)\s+\{(.*)$/){ %{$obj}=('origin'=>$host,'name'=>"$1",'class'=>'F5-vlan','desc'=>$z); $dl=__LINE__;	if($2 ne '}'){ $flag_more=1 } }
				elsif($z=~m/^\s+tag\s+(\d+).*$/ && $flag_more){ $obj=pop @objhost; $obj->{'vlan'}=$1; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\s+mac masq\s+([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}):([A-F\d]{2}).*$/i && $flag_more){
					$obj=pop @objhost; $obj->{'L2'}=lc("$1$2.$3$4.$5$6"); $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\s+interfaces\s+(.*)$/ && $flag_more){ $obj=pop @objhost; $obj->{'L1'}=$1; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^self\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\{(.*)$/){
					%{$obj}=('origin'=>$host,'name'=>"$1",'class'=>'F5-self','L3'=>"$1",'status'=>'enabled','desc'=>$z); $dl=__LINE__; if($2 ne '}'){ $flag_more=1 } }
				elsif($z=~m/^\s+netmask\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*$/ && $flag_more){
					$obj=pop @objhost; $obj->{'L3'} .= '/'.unpack('%32b*',inet_aton($1)); $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\s+vlan\s+(\S+).*$/ && $flag_more){ $obj=pop @objhost; $obj->{'vlan'}=$1; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^monitor\s+(\S+)\s+\{(.*)$/){ %{$obj}=('origin'=>$host,'name'=>"$1",'class'=>'F5-monitor','status'=>'enabled','desc'=>$z); $dl=__LINE__; if($2 ne '}'){ $flag_more=1 } }
				elsif($z=~m/^\s+defaults from\s+(\S+).*$/ && $flag_more){ $obj=pop @objhost; $obj->{'L4'}=$1; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\s+dest\s+(\S+):(\S+).*$/ && $flag_more){ $obj=pop @objhost; $obj->{'L3'}=$1; $obj->{'L4'}='tcp#'.$2; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^node\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+\{(.*)$/){
					%{$obj}=('origin'=>$host,'name'=>"$1",'class'=>'F5-node','L3'=>"$1",'status'=>'enabled','desc'=>$z); $dl=__LINE__; if($2 ne '}'){ $flag_more=1 } }
				elsif($z=~m/^\s+screen\s+(\S+).*$/ && $flag_more){ $obj=pop @objhost; $obj->{'name'}=$1; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^pool\s+(\S+)\s+\{(.*)$/){ $name=$1; $flag='pool'; &dbg("pool-name:$name\n") }
				elsif((($z=~m/^\s+members (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\S+)\s+\{.*$/)
					 ||($z=~m/^\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\S+)\s+\{.*$/))&&($flag eq 'pool')){
					%{$obj}=('origin'=>$host,'name'=>"$name",'class'=>'F5-pool','member'=>"$1:$2",'L3'=>$1,'L4'=>'tcp#'.$2,'status'=>'enabled','desc'=>$z); $dl=__LINE__ }
				elsif($z=~m/^virtual\s+(\S+)\s+\{(.*)$/){ %{$obj}=('origin'=>$host,'name'=>"$1",'class'=>'F5-virtual','status'=>'enabled','desc'=>$z); if($2 ne '}'){ $flag='virtual' }; $dl=__LINE__ }
				elsif($z=~m/^\s+destination\s+(\S+):(\S+).*$/ && ($flag eq 'virtual')){ $obj=pop @objhost; $obj->{'L3'}=$1; $obj->{'L4'}='#'.$2; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\s+ip protocol\s+(\S+).*$/ && ($flag eq 'virtual')){ $obj=pop @objhost; $obj->{'L4'}="$1".$obj->{'L4'}; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\s+session user disabled.*$/ && ($flag eq 'virtual')){ $obj=pop @objhost; $obj->{'status'}='disabled'; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				elsif($z=~m/^\}\s*$/){ $flag_more=0; $flag='' }
				elsif($flag_more){ $obj=pop @objhost; $obj->{'desc'} .= '#'.$z; $dl=__LINE__ }
				if(exists($$obj{'L3'})){ $obj->{'L3'}=~s/\*/any/ } }
			elsif($z=~m/(^|.*?\s+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d{0,2})(\s+.*|$)/){ 								# IP in unknown class
				$n=$2; 
				if($n ne '255.255.255.255'){
					%{$obj2}=('origin'=>$host,'name'=>$n,'class'=>'unknown/ignored','L3'=>$n,'desc'=>$z); $dl=__LINE__;
					&dbg(Dumper("Matched at script line:$dl (ignored)",$obj2)); if($Ignored){ push @objunk,$obj2 } $obj2={} 
			}	}
			elsif($z=~m/(^|.*?\s+)([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})(\s+.*|$)/i){											# MAC in unknown class
				$n=$2;
				if($n!~m/^ffff\.ffff\.ffff$/i){
					%{$obj2}=('origin'=>$host,'name'=>$n,'class'=>'unknown/ignored','L2'=>$n,'desc'=>$z); $dl=__LINE__;
					&dbg(Dumper("Matched at script line:$dl (ignored)",$obj2)); if($Ignored){ push @objunk,$obj2 } $obj2={} 
			}	}
			if(scalar %{$obj}){ &dbg(Dumper("Matched at script line:$dl",$obj)); push @objhost,$obj; $obj={} }				# If object not empty, add to list
		}close FILE;}
		while ($obj=shift @objhost){ push @objects,$obj }; undef @objhost;													# objhost (per host objects) used for better performance in lookups
		$now=time(); &dbg("> Finish processing          \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())." $file\n");
	}
	$n=scalar @objects;
	$now=time(); &dbg("#@# Finish file processing   \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())." objects:$n\n");
	@used=keys %{{ map { $_ => 1 } @used }}; 																				# Final updates/checks
	%{$hash}=map{$_ => 1} @used;
	while ($n--){
		$obj2=shift @objects;
		if(($obj2->{'class'} eq 'acl')&&($obj2->{'name'}=~/^(\S+) line .*/)){												# Interface to ACL match
			if(exists($$acl{$obj2->{'origin'}.":$1"})){ $obj2->{'L1'}=$acl->{$obj2->{'origin'}.":$1"} }else{ $obj2->{'status'}='N/A' } }
		if(($obj2->{'class'}=~m/community/)&&exists($$obj2{'L3'})&&exists($$hash{lc($obj2->{'origin'}.':vpn:'.$obj2->{'name'})})){ 
			push @used,lc($obj2->{'origin'}.':L3:'.$obj2->{'L3'}) } 														# Use of objects check (VPN peer)
		if(exists($$obj2{'member'})){ $obj2->{'member'}=~s/,$// }															# Format correction
		if(exists($$obj2{'L1'})){ $obj2->{'L1'}=~s/,$// }																	# Format correction
		if(exists($$obj2{'L2'})){ $obj2->{'L2'}=~s/,$// }																	# Format correction
		if(exists($$obj2{'vlan'})){ $obj2->{'vlan'}=~s/,$// }																# Format correction
		if(exists($$obj2{'L4'})){ $obj2->{'L4'}=~s/:#/#/g; $obj2->{'L4'}=~s/:eq#/#/g; $obj2->{'L4'}=~s/:$//; $obj2->{'L4'}=~s/,$// }			# Format correction
		if(exists($$obj2{'acl'})){ $obj2->{'acl'}=~s/,$// }																	# Format correction
		if(exists($$obj2{'L3'})){																							# create one object per IP address (except to ACL/NAT objects)
			@match=split(/,/, $obj2->{'L3'});
			if(!$expand){ $obj=$obj2; $obj->{'L3'}=''; }
			foreach $ip (@match){
				if($expand){ $obj=&copy_obj($obj2); }
				if($ip=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d+)?(\|\S+)?$/){
					($z,$p,$desc)=($1,$2,$3);
					if(!defined $p){																						# if no prefix, find the matching prefix
						$p=''; foreach $c (@match){ if($c=~m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d+).*$/){ $n1=$1; $n2=$2; if(&xlmatch(&xlcode($z,'L3'),&xlcode(&network("$n1$n2"),'L3'))){ $p=$n2 } } } }
					$ip="$z$p";	if(defined $desc){ $obj->{'desc'} .= "#$ip$desc" } 
				}
				if(!$expand){ $obj->{'L3'} .= $ip.','; }else{ $obj->{'L3'}=$ip; push @objects,$obj; $obj={}; }
			}
			if(!$expand){ $obj->{'L3'}=~s/, / /g; $obj->{'L3'}=~s/,$//g; push @objects,$obj; $obj={}; }
		}
		else{ push @objects,$obj2 };
	}
	undef $hash;
	$now=time(); &dbg("#@# Finish data checks       \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())." objects:".scalar(@objects)."             (".total_size(\@objects)." bytes)\n");
	#&dbg("#@# All objects\n".Dumper(@objects));
	if($ExpandGroups){																										# Group expansion to members
		#&dbg("#@# Members list\n".Dumper($members));
		$flag='';
		foreach $member (keys($members)){ 
			#&dbg("#@# Group object list expansion (before): ".$member." -> ".$members->{$member}."\n");
			(undef,$host,$map,$name)=split(/:/, $member); $members_aux->{$member}=&get_members($member,$host,$map,$members->{$member}); 		# Find members recursively
			if($member=~m/~/){ $c=''; foreach $n (split(/\s+/, $members_aux->{$member})){ $c .= '~'.$n.' '; }; $members_aux->{$member}=$c }		# Case of "not in"
			$members->{$member}=~s/ $//;
			#&dbg("#@# Group object list expansion (after ): ".$member." -> ".$members->{$member}."\n");
		}
		$now=time(); &dbg("#@# Finish member expansion  \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
		$members=$members_aux; undef $members_aux;
		if($TranslateNames){
			foreach $member (keys($members)){
				(undef,$host,$map,$name)=split(/:/, $member);
				#&dbg("#@# Group object list translation (before): ".$member." -> ".$members->{$member}."\n");
				foreach $n (split(/\s+/, $members->{$member})){ 															# Translate members
					$n=lc($n);
					if($n=~m/~/){ 																							# group with exclusions or "not in" case
						foreach $p (split(/~/, $n)){ 
							if(exists($$xlate{"t:$host:$map:".$p})){ 
								if($xlate->{"t:$host:$map:".$p}=~m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){ $members->{$member}=~s/(^|[^\w-])$p([^\w-]|$)/$1$xlate->{"t:$host:$map:".$p}$2\/32/gi }
								else{ $members->{$member}=~s/(^|[^\w-])$p([^\w-]|$)/$1$xlate->{"t:$host:$map:".$p}$2/gi } 
					}	}	} 
					elsif(exists($$xlate{"t:$host:$map:".$n})){ 
						if($xlate->{"t:$host:$map:".$n}=~m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){ $members->{$member}=~s/(^|[^\w-])$n([^\w-]|$)/$1$xlate->{"t:$host:$map:".$n}$2\/32/gi }
						else{ $members->{$member}=~s/(^|[^\w-])$n([^\w-]|$)/$1$xlate->{"t:$host:$map:".$n}$2/gi }
				}	}
				$members->{$member}=~s/(^|\s)(\S+)\s(?=.*\2(\s|$))/$1/g;													# Eliminate duplicates
				$members->{$member}=~s/:#/#/g; $members->{$member}=~s/:eq#/#/g; $members->{$member}=~s/ $//;
				#&dbg("#@# Group object list translation (after ): ".$member." -> ".$members->{$member}."\n");
			}	
			$now=time(); &dbg("#@# Finish translate objects \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
		}
		foreach $x (@objects){
			$host=$x->{'origin'}; ($obj,$obj2)=({},{}); 
			($flag,$src,$dst,$prot,$srvc,$srvc2,$src_t,$dst_t,$srvc_t)=('','','','*','*','*','*','*','*');
			($src_m,$dst_m,$prot_m,$srvc_m,$srvc2_m,$src_t_m,$dst_t_m,$srvc_t_m,$src_mm,$dst_mm,$prot_mm,$srvc_mm,$srvc2_mm,$src_t_mm,$dst_t_mm,$srvc_t_mm)=('*','*','*','*','*','*','*','*','*','*','*','*','*','*','*','*');
			$obj=&copy_obj($x);
			if($obj->{'class'} eq 'acl'){																					# Expand members in ACL rules
				if($obj->{'L3'}=~m/^(\S+) -> (\S+)$/){ $src=$1; $dst=$2 }else{ $src=$obj->{'L3'}; $dst='*' };				# check L3 section
				foreach $n (split(/,/, $src.','.$dst)){ push @used,(lc("$host:L3:$n")) }
			}
			elsif($obj->{'class'} eq 'nat'){																				# Expand members in NAT rules
				if($obj->{'L3'}=~m/^(\S+) -> (\S+) <> (\S+) -> (\S+)$/){ $src=$1; $dst=$2; $src_t=$3; $dst_t=$4 }			# check L3 section
				elsif($obj->{'L3'}=~m/^(\S+) -> (\S+) <> (\S+)$/){ $src=$1; $dst=$2; $src_t=$3; $dst_t='*' }
				elsif($obj->{'L3'}=~m/^(\S+) <> (\S+)$/){ $src=$1; $dst='*'; $src_t=$2; $dst_t='*' }
				else { $src=$obj->{'L3'}; $dst='*'; $src_t='*'; $dst_t='*' };
				foreach $n (split(/,/, $src.','.$dst.','.$src_t.','.$dst_t)){ push @used,(lc("$host:L3:$n")) }
			}
			elsif(exists($$obj{'L3'})){																						# other L3 objects
				$src=$obj->{'L3'}; $dst='*'; $src_t='*'; $dst_t='*';
				foreach $n (split(/,/, $src)){ push @used,(lc("$host:L3:$n")) }
			}
			foreach $n (split(/,/, $src  )){ if($src_m   eq '*'){ $src_m  ='' } if(exists($$members{'m:'.$obj->{'origin'}.':L3:'.$n})){ $src_m   .= $members->{'m:'.$obj->{'origin'}.':L3:'.$n}.' '; $flag="expand" }else{ $src_m   .= $n.' ' } }
			foreach $n (split(/,/, $dst  )){ if($dst_m   eq '*'){ $dst_m  ='' } if(exists($$members{'m:'.$obj->{'origin'}.':L3:'.$n})){ $dst_m   .= $members->{'m:'.$obj->{'origin'}.':L3:'.$n}.' '; $flag="expand" }else{ $dst_m   .= $n.' ' } }
			foreach $n (split(/,/, $src_t)){ if($src_t_m eq '*'){ $src_t_m='' } if(exists($$members{'m:'.$obj->{'origin'}.':L3:'.$n})){ $src_t_m .= $members->{'m:'.$obj->{'origin'}.':L3:'.$n}.' '; $flag="expand" }else{ $src_t_m .= $n.' ' } }
			foreach $n (split(/,/, $dst_t)){ if($dst_t_m eq '*'){ $dst_t_m='' } if(exists($$members{'m:'.$obj->{'origin'}.':L3:'.$n})){ $dst_t_m .= $members->{'m:'.$obj->{'origin'}.':L3:'.$n}.' '; $flag="expand" }else{ $dst_t_m .= $n.' ' } }
			if(exists($$obj{'L4'})){																					# check L4 section
				if($obj->{'L4'}=~m/^(\S+) <> (\S+)$/){ $srvc=$1; $srvc_t=$2 }
				else{ $prot='*'; $srvc=$obj->{'L4'}; $srvc_t='*' }														# Sometimes prot but same behavior
				
				foreach $n (split(/,/, $srvc)){ if($n=~m/^(\S+)?&src:(\S+)$/){ push @used,(lc("$host:L4:$n")); if($1){ $srvc=$1 }; $srvc2=$2 }; if($n=~m/^(\S+):(\S+)$/){ $prot=$1; $srvc=$2 } }
				if($srvc2=~m/^(\S+):(\S+)$/){ $prot=$1; $srvc2=$2 }; 
				if($srvc_t=~m/^(\S+):(\S+)$/){ $prot=$1; $srvc_t=$2 } 
				foreach $n (split(/,/, $srvc)){ push @used,(lc("$host:L4:$srvc")) }; push @used,(lc("$host:L4:$prot"),lc("$host:L4:$srvc2"),lc("$host:L4:$srvc_t"));
				if(exists($$members{'m:'.$obj->{'origin'}.':L4:'.$prot})){   $prot_m  =$members->{'m:'.$obj->{'origin'}.':L4:'.$prot};   $flag="expand" }else{ $prot_m=$prot }
				foreach $n (split(/,/, $srvc)){ if($srvc_m eq '*'){ $srvc_m='' } if(exists($$members{'m:'.$obj->{'origin'}.':L4:'.$n})){ $srvc_m .= $members->{'m:'.$obj->{'origin'}.':L4:'.$n}.' ';   $flag="expand" }else{ $srvc_m .= $n.' ' } }
				if(exists($$members{'m:'.$obj->{'origin'}.':L4:'.$srvc2})){  $srvc2_m =$members->{'m:'.$obj->{'origin'}.':L4:'.$srvc2};  $flag="expand" }else{ $srvc2_m=$srvc2 }
				if(exists($$members{'m:'.$obj->{'origin'}.':L4:'.$srvc_t})){ $srvc_t_m=$members->{'m:'.$obj->{'origin'}.':L4:'.$srvc_t}; $flag="expand" }else{ $srvc_t_m=$srvc_t } 
			}
			if($flag eq "expand"){																							# Expansion of groups to members
				foreach $n ($src_m,$dst_m,$prot_m,$srvc_m,$srvc2_m,$src_t_m,$dst_t_m,$srvc_t_m){ $n=~s/ $// }
				if($obj->{'class'} eq 'acl'){																				# for ACL rules
					#&dbg("Expand ACL($host):".$src.' -> '.$dst.'|'.$prot.':'.$srvc.' -> '.$srvc2," with ",$src_m.' -> '.$dst_m.'|'.$prot_m.':'.$srvc_m.' -> '.$srvc2_m,"\n");
					if(!$expand){
						foreach $n ($src_m,$dst_m,$prot_m,$srvc_m,$srvc2_m){ $n=~s/\s+/,/g; $n=~s/,$// }
						($src_mm,$dst_mm,$prot_mm,$srvc_mm,$srvc2_mm)=($src_m,$dst_m,$prot_m,$srvc_m,$srvc2_m);
						$obj2=$obj; &expand();
					}else{
						foreach $src_mm   (split(/\s+/, $src_m)){
						foreach $dst_mm   (split(/\s+/, $dst_m)){
						foreach $prot_mm  (split(/\s+/, $prot_m)){
						foreach $srvc_mm  (split(/\s+/, $srvc_m)){
						foreach $srvc2_mm (split(/\s+/, $srvc2_m)){
							$obj2=&copy_obj($obj); &expand();
				}	}	}}}}}
				elsif($obj->{'class'} eq 'nat'){																			# for NAT rules
					#&dbg("Expand NAT($host):".$src.' -> '.$dst.' <> '.$src_t.' -> '.$dst_t.'|'.$prot.':'.$srvc.' -> '.$srvc_t." with ",$src_m.' -> '.$dst_m.' <> '.$src_t_m.' -> '.$dst_t_m.'|'.$prot_m.':'.$srvc_m.' -> '.$srvc_t_m."\n");
					if(!$expand){
						foreach $n ($src_m,$dst_m,$src_t_m,$dst_t_m,$prot_m,$srvc_m,$srvc_t_m){ $n=~s/\s+/,/g }
						($src_mm,$dst_mm,$src_t_mm,$dst_t_mm,$prot_mm,$srvc_mm,$srvc_t_mm)=($src_m,$dst_m,$src_t_m,$dst_t_m,$prot_m,$srvc_m,$srvc_t_m);
						$obj2=$obj; &expandNAT();
					}else{
						foreach $src_mm    (split(/\s+/, $src_m)){
						foreach $dst_mm    (split(/\s+/, $dst_m)){
						foreach $src_t_mm  (split(/\s+/, $src_t_m)){
						foreach $dst_t_mm  (split(/\s+/, $dst_t_m)){
						foreach $prot_mm   (split(/\s+/, $prot_m)){
						foreach $srvc_mm   (split(/\s+/, $srvc_m)){
						foreach $srvc_t_mm (split(/\s+/, $srvc_t_m)){
							$obj2=&copy_obj($obj); &expandNAT();
				}	}	}}}}}}}		
				elsif(exists($$obj{'L3'})){
					if(!$expand){ $src_m=~s/\s+/,/g; ($src_mm)=($src_m); $obj2=$obj; &expand() }else{ foreach $src_mm    (split(/\s+/, $src_m)){ $obj2=&copy_obj($obj); &expand() } }
				}
				elsif(exists($$obj{'L4'})){
					if(!$expand){ $srvc_m=~s/\s+/,/g; ($srvc_mm)=($srvc_m); $obj2=$obj; &expand() }else{ foreach $srvc_mm    (split(/\s+/, $srvc_m)){ $obj2=&copy_obj($obj); &expand() } }
				}
			}		
			else { push @xtend,$obj }
		}
		$now=time(); &dbg("#@# Finish group expansion   \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())." objects:".scalar(@objects)." xtend:".scalar(@xtend)." (".total_size(\@xtend)." bytes)\n")
	}
	else { foreach $x (@objects){ $obj={}; $obj=&copy_obj($x); push @xtend,$obj; } }										# if no expansion, create xtend array by copy of objects array
	while ($obj2=shift @objunk){ push @xtend,$obj2 }; undef @objunk;														# move objects from objunk to extended
	#&dbg("#@# All xtend objects\n".Dumper(@xtend));
	$auxt={%$members,%$xlate,%$domain,%$user,%$policy}; $tb->{'X3-Aux'}=$auxt; undef $members; undef $user;	undef $policy;	# group objects for Aux table
	$now=time(); &dbg("#@# Finish Xtend+Aux table   \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())." objects:".scalar(@objects)." xtend:".scalar(@xtend)." (".total_size(\@xtend)." bytes)\n");
	if($TranslateNames){																									# Translate object names
		$c=scalar @xtend;
		foreach $obj (@xtend){
			$now=time(); if($now>$before+600){ &dbg(sprintf("#@# Xlate+Xcode              \@time %8.2f   %8d left\n",$now-$start,$c)); $before=$now }; $c--; 		# Prints status every 10 minutes
			$host=$obj->{'origin'};
			if(exists($$obj{'L3'})){																						# IP translation
				if((($obj->{'class'} eq 'acl')  &&($obj->{'L3'}=~m/^(\S+) -> (\S+)$/))
				 ||(($obj->{'class'} eq 'nat')  &&($obj->{'L3'}=~m/^(\S+) -> (\S+) <> (\S+) -> (\S+)$/))
				 ||(($obj->{'class'} eq 'nat')  &&($obj->{'L3'}=~m/^(\S+) -> (\S+) <> (\S+)$/))
				 ||(($obj->{'class'} eq 'nat')  &&($obj->{'L3'}=~m/^(\S+) <> (\S+)$/))
				 ||(($obj->{'class'} eq 'route')&&($obj->{'L3'}=~m/^(\S+) => (\S+)$/))
				 ||((($obj->{'class'} ne 'acl')||($obj->{'class'} ne 'nat')||($obj->{'class'} ne 'route'))&&($obj->{'L3'}=~m/^(\S+)$/))){
					$n1=lc($1);
					if(defined($2)){ $n2=lc($2) }else{ $n2="*EMPTY*" }; if(defined($3)){ $n3=lc($3) }else{ $n3="*EMPTY*" }; if(defined($4)){ $n4=lc($4) }else{ $n4="*EMPTY*" };
					#&dbg("Xlate L3: $obj->{'L3'}: $n1 $n2 $n3 $n4\n");
					foreach $c ($n1,$n2,$n3,$n4){ 
						foreach $n (split(/,/, $c)){
							if(exists($$xlate{"t:$host:L3:".$n})){ 															# Push to used objects and translate
								push @used,lc("$host:L3:$n");
								$obj->{'L3'}=~s/(^|[^\w-])$n([^\w-]|$)/$1$xlate->{"t:$host:L3:".$n}$2/gi }
							elsif($n=~m/(\S+)\/[\d]{1,2}/){ $n=lc($1); if(exists($$xlate{"t:$host:L3:".$n})){ push @used,lc("$host:L3:$n"); $obj->{'L3'}=~s/(^|[^\w-])$n([^\w-]|$)/$1$xlate->{"t:$host:L3:".$n}$2/gi } }
							elsif($n=~m/(\S+)~(\S+)/){ 																		# Group with exceptions case
								$n=lc($1); $x=lc($2); 
								if(exists($$xlate{"t:$host:L3:".$n})){ 
									push @used,lc("$host:L3:$n"); 
									$obj->{'L3'}=~s/(^|[^\w-])$n([^\w-]|$)/$1$xlate->{"t:$host:L3:".$n}$2/gi }
								if(exists($$xlate{"t:$host:L3:".$x})){ 
									push @used,lc("$host:L3:$x"); 
									$obj->{'L3'}=~s/(^|[^\w-])$x([^\w-]|$)/$1$xlate->{"t:$host:L3:".$x}$2/gi } }
							$obj->{'L3'}=~s/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/$1\/32/gi; $obj->{'L3'}=~s/\/32\//\//gi; 
				}	}	}
				elsif(exists($$xlate{"t:$host:L3:".lc($obj->{'L3'})})){ 
					push @used,lc("$host:L3:".$obj->{'L3'}); $obj->{'L3'}=$xlate->{"t:$host:L3:".lc($obj->{'L3'})} }
				if($obj->{'L3'}=~m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/){ $obj->{'L3'}.='/32' }
			}
			if(exists($$obj{'L4'})){																						# Service protocol/port translation
				@match=$obj->{'L4'}=~m/#([\w-]+)/g;
				#&dbg(Dumper("Xlate L4: $obj->{'L4'}",@match));
				foreach $map (@match){																						# Replace Service names by Service port number
					if(exists($$ServicePort::services{$map})){ $obj->{'L4'}=~s/#$map([^\w-]|$)/#$ServicePort::services->{$map}$1/gi } }
				if((($obj->{'class'} eq 'acl')&&($obj->{'L4'}=~m/^(\S+)$/))||(($obj->{'class'} eq 'nat')&&($obj->{'L4'}=~m/^(\S+) <> (\S+)$/))){
					$n1=lc($1);
					if(defined($2)){ $n2=lc($2) }else{ $n2="*EMPTY*" };
					#&dbg("Xlate L4: $obj->{'L4'}: $n1 $n2\n");
					foreach $c ($n1,$n2){
						foreach $n (split(/,/, $c)){
							if($n=~m/\S+:(\S+)/){ $n=$1 };
							if(exists($$xlate{"t:$host:L4:".$n})){
								push @used,lc("$host:L4:$n"); 																# Use of object check
								$obj->{'L4'}=~s/(^|[^\w-])$n([^\w-]|$)/$1$xlate->{"t:$host:L4:".$n}$2/gi					# Translation
			}	}	}	}	}
			if($XLcode&&(exists($$obj{'L3'})||exists($$obj{'L4'}))&&($obj->{'class'} ne 'unknown/ignored')){				# Xlcode generation
				$ip=''; $srvc=''; $ext='';
				if($obj->{'class'} eq 'acl'){ $map='a' } elsif($obj->{'class'} eq 'nat'){ $map='n' } elsif($obj->{'class'} eq 'route'){ $map='r' } else { $map='o' }
				if(exists($$obj{'L3'})){																					# XLcode of L3 part (X to X)
					if($obj->{'L3'}=~m/^(\S+) -> (\S+)$/){ $ip=&xlcode($2,'L3').'Y'.&xlcode($1,'L3').'ZK' }
					elsif($obj->{'L3'}=~m/^(\S+) -> (\S+) <> (\S+) -> (\S+)$/){ $ip=&xlcode($2,'L3').'Y'.&xlcode($1,'L3').'Z'.&xlcode($4,'L3').'K'.&xlcode($3,'L3') }
					elsif($obj->{'L3'}=~m/^(\S+) -> (\S+) <> (\S+)$/){ $ip=&xlcode($2,'L3').'Y'.&xlcode($1,'L3').'ZK'.&xlcode($3,'L3') }
					elsif($obj->{'L3'}=~m/^(\S+) <> (\S+)$/){ $ip='Y'.&xlcode($1,'L3').'ZK'.&xlcode($2,'L3') }
					elsif($obj->{'L3'}=~m/^(\S+) => (\S+)$/){ $ip=&xlcode($1,'L3').'Y'.&xlcode($2,'L3').'ZK' }
					else{ $ip=&xlcode($obj->{'L3'},'L3').'YZK'}
					if($obj->{'L3'}=~m/~/){ $ext .= 'e'} }
				if(exists($$obj{'L4'})){																					# XLcode of L4 part (L to L)
					if($obj->{'L4'}=~m/^(\S+) <> (\S+)$/){ $srvc=&xlcode($1,'L4').'M'.&xlcode($2,'L4'); $prot=$2 }
					else{ $srvc=&xlcode($obj->{'L4'},'L4'); if($srvc!~m/M/){ $srvc .= 'M' }; $prot=$obj->{'L4'} }
					$x='';
					foreach $c (split(/,/, $prot)){
						if($c eq '#0'){ } # do nothing
						elsif($c=~m/^([^#]+)/){ $aux=$1; if($aux=~m/^(\S+)(:eq|:lt|:gt|:range)$/){ $aux=$1 }; $x .= lc($aux).'|' }
						else{ &dbg(Dumper("*NOT-MATCHED* XLcode",$obj)) }
					}
					$x=~s/\|$//; $srvc .= 'N'.$x;
					if($obj->{'L4'}=~m/neq#/){ $ext .= 'f'} if($obj->{'L4'}=~m/&src/){ $ext .= 's'} }
				if(exists($$obj{'status'})){ if($obj->{'status'}=~m/Timed/){ $ext .= 't'} };
				$obj->{'XLcode'}=$map.'X'.$ip.'XL'.$srvc.'L'.$ext;
				#&dbg("XLcode\n".Dumper($obj))
		}	}
		$now=time(); &dbg("#@# Finish Xlate + XLcode    \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
	}
	undef $xlate;
	if($Covered){																											# Check if a rule is later covered by another rule
		$n=scalar @xtend; $hash={}; @match=(); $before=0; $c=$n; $n5=0;														# Tag covered rules in xtend array
		while($n-->0){ if(($xtend[$n]->{'class'} eq 'acl')&&($xtend[$n]->{'status'} eq 'on')){ unshift @match,$n } }		# create list of candidate entries for inspection
		$aux=scalar @match; &dbg("#@# Covered. 10 min status ($aux out of $c rows)\n"); $c=$aux;	
		while($aux-->0){ 
			$p=$aux; $n=$match[$aux]; $ext='X';
			$host=$xtend[$n]->{'origin'}; ($n1,undef,$n3)=$xtend[$n]->{'name'}=~m/^(\S+) (line|rule) (\d+)$/; $hash->{"$host:$n1:$n3"}=1; # Hash of not matched rules
			$now=time(); if($now>$before+600){ &dbg(sprintf("#@# Covered. %8.2f%% $host ACL:$n1 %8d hits (time %8.2f)\n",100-100*$aux/$c,$n5,$now-$start)); $before=$now }	# Prints status every 10 minutes
			while($p-->0){
				$z=$match[$p]; ($n2,undef,$n4)=$xtend[$z]->{'name'}=~m/^(\S+) (line|rule) (\d+)$/;
				if(($xtend[$z]->{'origin'} eq $host)&&($n1 eq $n2)){														# Check if same device and acl
					if(($n3 ne $n4)&&($xtend[$z]->{'vpn'} eq $xtend[$n]->{'vpn'})){											# Conditions needed to check Covered
						if($xtend[$z]->{'action'} ne $xtend[$n]->{'action'}){ $ext='W' }		 							# tags W when intermediate rule does different action
						elsif((&xlmatch($xtend[$z]->{'XLcode'},$xtend[$n]->{'XLcode'},'quick')==0b111111111)){ 
							$xtend[$z]->{'status'} .= "$ext$n3"; $n5++; splice(@match,$p,1); $aux-- } 
					}elsif($xtend[$z]->{'status'} ne 'on'){ splice(@match,$p,1); $aux-- }
				}
				else{ $p=0 } 																								# if different host or ACL name stop second loop
				#$now=time(); &dbg("#@# Subcycle $z on $n completed \@time:".sprintf("%8.2f", $now-$start)."\n");
			}
			#$now=time(); &dbg("#@# Cycle $n completed \@time:".sprintf("%8.2f", $now-$start)."\n");
		};	
		$now=time(); &dbg(sprintf("#@# Covered.  100.00%% $host ACL:$n1 %8d hits (time %8.2f)\n",$n5,$now-$start));			# Prints final status
		#&dbg("Rules not completely covered:\n".Dumper($hash))
		foreach $obj2 (@objects){																							# Tag covered rules in object array
			if(($obj2->{'class'} eq 'acl')&&($obj2->{'status'} eq 'on')){
				$host=$obj2->{'origin'}; ($n1,undef,$n2)=$obj2->{'name'}=~m/^(\S+) (line|rule) (\d+)$/; if(!exists($$hash{"$host:$n1:$n2"})){ $obj2->{'status'} .= 'XW' }
		}	}
		undef $hash; undef @match;
		$now=time(); &dbg("#@# Finish Covered Rule Tag  \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
	}
	if($NotFound){																											# Remmove/Tag unused objects
		#&dbg("#@# Used group/member object list\n".Dumper($members_used));
		foreach $member (keys($members_used)){																				# Put members_used in used list
			foreach $x (split(/\s+/, $members_used->{$member})){ push @used,$x }
		}
		%{$hash}=map{$_ => 1} @used; undef @used; undef $members_used;														# Make unique key values and clean members_used (no longer needed)
		#&dbg("#@# Used unique object list\n".Dumper($hash));
		$n=scalar @objects;																									# Run the object array and remove not used
		while ($n--){
			$obj2=shift @objects;
			if($obj2->{'class'}=~m/^(ASA-|CHK-).*$/i){
				if(exists($$domain{'d:'.$obj2->{'origin'}.':'.$obj2->{'name'}})){											# Use of VPN Domain groups
					if(exists($$hash{lc($obj2->{'origin'}.':L3:'.$domain->{'d:'.$obj2->{'origin'}.':'.$obj2->{'name'}})})){ push @objects,$obj2 } }
				elsif(exists($$hash{lc($obj2->{'origin'}.':L3:'.$obj2->{'name'})})											# Use of object
					||exists($$hash{lc($obj2->{'origin'}.':L4:'.$obj2->{'name'})})
					||exists($$hash{lc($obj2->{'origin'}.':vpn:'.$obj2->{'name'})})){	push @objects,$obj2
			}	}
			elsif(exists($$obj2{'status'})){																				# Remove not active objects
				$status=$obj2->{'status'}; if(($status!~m/disabled|down|notconnec|sfpInvali|sfpAbsent|err-disabled|off|N\/A|Backup/)){ push @objects,$obj2 } }
			else{ push @objects,$obj2 }
		}
		foreach	$obj2 (@xtend){																								# Run the xtend array and tag not used
			if($obj2->{'class'}=~m/^(ASA-|CHK-).*$/i){
				if(exists($$domain{'d:'.$obj2->{'origin'}.':'.$obj2->{'name'}})){
					if(!exists($$hash{lc($obj2->{'origin'}.':L3:'.$domain->{'d:'.$obj2->{'origin'}.':'.$obj2->{'name'}})})){ $obj2->{'status'}='N/A' } }
				elsif(!(exists($$hash{lc($obj2->{'origin'}.':L3:'.$obj2->{'name'})})
					  ||exists($$hash{lc($obj2->{'origin'}.':L4:'.$obj2->{'name'})})
					  ||exists($$hash{lc($obj2->{'origin'}.':vpn:'.$obj2->{'name'})}))){ $obj2->{'status'}='N/A'
		}	}	}
		$now=time(); &dbg("#@# Finish NotFound object   \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n")
	}
	if($NetList){																											# IP/Network Use List and Layouts
		$n=scalar @xtend; $hash={}; $map={}; $mac={}; $flag=''; $flag_more='';
		while ($n-->0){
			$obj2=$xtend[$n]; $host=lc($obj2->{'origin'}); $flag='';
			#&dbg(Dumper("Layouts:",$obj2));
			if($LayPhysical){																								# Physical Layout creation
				if(($obj2->{'class'} eq 'interface')||(($obj2->{'class'} eq 'arp')&&exists($$obj2{'L1'}))||($obj2->{'class'} eq 'stp')){
					$layphy_v->{$host}="Fuchsia||||||$host|||$host"; if(!$obj2->{'L1'}){ print Dumper($obj2)} 
					#&dbg("layphy_v->{$host} line ".__LINE__."\n"); 
					$if=lc($obj2->{'L1'}); $c='Yellow';
					if($if=~m/^Self$/i){ $if='self' }elsif($if=~m/^(lo|po|vp|vl|\S+\.)\d+$/i){ $c='Lime' }; 
					if(exists($$obj2{'status'})){ if($obj2->{'status'}=~m/disabled|down|notconnec|sfpInvali|sfpAbsent|err-disabled/){ $c='Orange' }elsif($obj2->{'status'}=~m/Backup/){ $c='White' } };
					if($if=~m/^(\S+)\.(\d+)$/){										 										# Action for vlan interfaces
						$layphy_v->{lc("~vlan~:$2")}="Lime|||||Skip|Vl$2|||Vl$2"; 
						#&dbg("layphy_v->{~vlan~:$2} line ".__LINE__."\n");
						$obj={}; %{$obj}=('node1'=>"$host:$if",'node2'=>lc("~vlan~:$2"),'color'=>'Lime'); push @layphy_e,$obj; 
						#&dbg(Dumper("layphy_e",$obj));
						$obj={}; %{$obj}=('node1'=>"$host:$if",'node2'=>lc("$host:$1"),'color'=>'Lime'); push @layphy_e,$obj; 
						#&dbg(Dumper("layphy_e",$obj)) 
					}
					if($if!~m/^(\S+ > \S+)|(vl\d+)|(Self)$/i){		 									# interface to device connection (ignore vlan and self interfaces)
						$layphy_v->{lc("$host:$if")}="$c|||||Skip|$if|||$host:$if"; 
						#&dbg("layphy_v->{$host:$if} line ".__LINE__."\n");
						$obj={}; %{$obj}=('node1'=>$host,'node2'=>"$host:$if",'color'=>$c); push @layphy_e,$obj; 
						#&dbg(Dumper("layphy_e",$obj)) 
					}
					if(exists($$obj2{'member'})){ 																			# connect interface to port-channel group
						$if2=lc($obj2->{'member'}); $layphy_v->{"$host:$if2"}="Lime|||||Skip|$if2|||$host:$if2"; 
						#&dbg("layphy_v->{$host:$if2} line ".__LINE__."\n");
						$obj={}; %{$obj}=('node1'=>"$host:$if",'node2'=>"$host:$if2",'color'=>'White'); push @layphy_e,$obj;  
						#&dbg(Dumper("layphy_e",$obj));
						if($obj2->{'name'}=~m/\S+ (FEX-\d+)/){ 																# Nexus: Port-Channel to FEX connection
							$layphy_v->{lc("fex:$1")}="White||||||$1|||FEX:$1"; 
							#&dbg("layphy_v->{FEX:$1} line ".__LINE__."\n");
							$obj={}; %{$obj}=('node1'=>"$host:$if2",'node2'=>lc("fex:$1"),'color'=>'White'); push @layphy_e,$obj; 
							#&dbg(Dumper("layphy_e",$obj));
					}	}
					if(exists($$obj2{'L2'})){																				# MAC address association
						$x=lc($obj2->{'L2'}); @match=split(/,/,$x);
						foreach $x (@match){
							if($x=~m/(0000\.0c07\.ac|0000\.0c9f\.f|0000\.5e00\.01|0007\.b4|0000\.0000\.fe|0098.7654.3210)/){ $c='Red' }else{ $c='Aqua' }; 
							$layphy_v->{$x}="$c|||||Skip|$x|||$x"; 
							#&dbg("layphy_v->{$x} line ".__LINE__."\n");
							if(($if=~m/^vl(\d+)$/i)||($if=~m/^\S+\.(\d+)$/)){ 
								$mac->{$x} .= "~vlan~:$1;" unless (exists($$mac{$x})&&(index($mac->{$x},"~vlan~:$1;")!=-1)); 
								$layphy_v->{"~vlan~:$1"}="Lime|||||Skip|Vl$1|||Vl$1"; 
								#&dbg("layphy_v->{~vlan~:$1} line ".__LINE__."\n") 
							}
							elsif($if!~m/\S+ > \S+/){
								if(($if eq 'self')&&exists($$mac{$x})){
									@buffer=$mac->{$x}=~m/([^;]+):self;/g; 														# lookup two switches with same Bridge ID (Virtual Switch)
									foreach $z (@buffer){ 
										$obj={}; %{$obj}=('node1'=>"$host",'node2'=>"$z",'color'=>'Fuchsia'); push @layphy_e,$obj;  
										#&dbg(Dumper("layphy_e",$obj)) 
								} 	}
								$mac->{$x} .= "$host:$if;" unless (exists($$mac{$x})&&(index($mac->{$x},"$host:$if;")!=-1)); undef @buffer;
					}	}	}
					if(exists($$obj2{'status'})&&($obj2->{'status'} eq 'Sw-Sw')){ if($obj2->{'L1'}=~m/^(\S+) > (\S+)$/i){ ($n1,$n2)=($1,$2); $map->{"$host:$n1"}=$obj2->{'L2'}.":$n2" }	}# switch to switch info
				}
				elsif($obj2->{'class'} eq 'vlan'){
					$aux=$obj2->{'vlan'}; $layphy_v->{"~vlan~:$aux"}="Lime|||||Skip|Vl$aux|||Vl$aux"; 
					#&dbg("layphy_v->{~vlan~:$aux} line ".__LINE__."\n");
					@match=split(/,/,$obj2->{'L1'});
					foreach $if (@match){ 
						$obj={}; %{$obj}=('node1'=>"$host:$if",'node2'=>"~vlan~:$aux",'color'=>'Lime'); push @layphy_e,$obj; 
						#&dbg(Dumper("layphy_e",$obj));  
			}	}	}
			if(exists($$obj2{'XLcode'})&&($obj2->{'class'} ne 'name')&&((!exists($$obj2{'status'}))||(exists($$obj2{'status'})&&$obj2->{'status'}!~m/^(N\/A)|(on[XW]\d+)$/))){
				if($obj2->{'name'}=~m/^(\S+) (line|rule) \d+$/){ $name=$1 }else{ $name=$obj2->{'name'} }; $name=$name.'@'.$host.':'.$obj2->{'class'}.';';
				($n1,$n2,$n3,$n4)=$obj2->{'XLcode'}=~m/X([0-9a-f\|-]+)?Y?([0-9a-f\|-]+)?Z?([0-9a-f\|-]+)?K?([0-9a-f\|-]+)?X/; 
				foreach $z ($n1,$n2,$n3,$n4){
					if($z){
						foreach $c (split(/\|/,$z)){
							if($c){
								$x=&uncodeXL($c); if($x=~m/^\S+-\S+$/){ $flag='Yellow'; $ext='' }else{ $flag='Aqua'; $ext='Skip' };	# Get decimal IP of xlcode network
								if(exists($$net{"$c|$x"})){ if(index($net->{"$c|$x"},$name)==-1){ $net->{"$c|$x"} .= $name } }		# Build netList object
								else{ 
									$net->{"$c|$x"}=$name; if($LayNetwork){ $laynet_v->{"$c"}="$flag|||||$ext|$x|||$x" }; 	# Network Layout creation
									#&dbg("laynet_v->{$c\|$x} line ".__LINE__)
				}	}	}	}	}
				if($LayLogical){																							# Logical Layout creation
					if($obj2->{'class'} eq 'interface'){
						foreach $ip (split(/\,/,$obj2->{'L3'})){
							$x=&xlcode($ip,'L3'); $z=&network($ip); $p=&xlcode($z,'L3');
							$laylog_v->{$host}="Fuchsia||||||$host|||$host"; if($z!~m/^.*\/32$/){ $flag='Yellow';$ext='' }else{ $flag='Aqua';$ext='Skip' }; $laylog_v->{$p}="$flag|||||$ext|$z|||$z"; 
							if(exists($$laylog_v{$x})){ $laylog_v->{$x} .= "\@$host;" }else{ $laylog_v->{$x}="Aqua|||||Skip|$ip|||$ip" }; 
							#&dbg("laylog_v->{$host + $x + $p} line ".__LINE__."\n");
							$obj={}; %{$obj}=('node1'=>$host,'node2'=>$x,'color'=>'Aqua'); push @laylog_e,$obj; 
							#&dbg(Dumper("laylog_e",$obj));
							if($z!~m/^.*\/32$/){ 
								$obj={}; %{$obj}=('node1'=>$x,'node2'=>$p,'color'=>'White'); push @laylog_e,$obj; 
								#&dbg(Dumper("laylog_e",$obj)); 
								$obj={}; %{$obj}=('node1'=>$host,'node2'=>$p,'color'=>'Yellow'); push @laylog_e,$obj; 
								#&dbg(Dumper("laylog_e",$obj))
					}	}	}
					if($obj2->{'class'} eq 'route'){
						($n1,$n2)=$obj2->{'L3'}=~m/^(\S+) => (\S+)$/; ($x)=$obj2->{'XLcode'}=~m/X([0-9a-f\|-]+)Y/; $x='00000000-ffffffff' unless defined $x;
						$hash->{"$host:$n1:$n2"}=$x; if($obj2->{'XLcode'}=~m/Y([0-9a-f\|-]+)Z/){ $hash->{"$host:$n1:$n2"} .= ":$1" }		# hash of routes
				}	}
				if($LayFlow){																								# Permitted flows Layout creation
					use List::MoreUtils qw( each_array );
					if(($obj2->{'class'} eq 'acl')&&($obj2->{'action'}=~m/^(permit|accept)$/)&&($obj2->{'status'}=~m/^on$/)){
						($srvc,$srvc2,$prot)=$obj2->{'XLcode'}=~m/L([0-9a-f\|-]+)?M?([0-9a-f\|-]+)?N?(\S+)?L/;
						#&dbg(Dumper("LayFlow (dst,prot,srvc,srvc-src,src:",$n1,$prot,$srvc,$srvc2,$n2)); 
						$layflow_v->{$host}="Fuchsia||||||$host|||$host"; 													# Device
						#&dbg("layflow_v->{$host} line ".__LINE__."\n");
						if(defined $n1){ 																					# Destination IP
							foreach $c (split(/\|/,$n1)){
								$x=&uncodeXL($c); 
								$layflow_v->{"$c"}="Yellow||||||$x|||$x"; 
								#&dbg(Dumper("Dst:",$layflow_v->{$c})); 
								$obj={}; %{$obj}=('node1'=>$host,'node2'=>$c,'color'=>'Yellow'); push @layflow_e,$obj; 		# Link FW-Dst
								#&dbg(Dumper("layflow_e",$obj));			
						}	}
						else{ $n1="00000000-ffffffff" }
						if((defined $srvc)||(defined $prot)){																# Destination Service
							($x,$n3,$n4,$n5,$n6,$n7,$n8)=('','','','','','','');
							if((defined $srvc)&&($srvc!~m/^\|+$/)){	
								if($prot){ 
									@a=split(/\|/,$prot); @b=split(/\|/,$srvc); $p=each_array(@a,@b); $n8='';
									while(($n3,$n4)=$p->()){ if($n4){ $n8 .= "$n3#$n4|" }else{ $n8 .= "$n3|" } } $n8=~s/\|$//;
								}else{ $n8=$srvc }
							}
							else{ $n8=$prot }
							foreach $n7 (split(/\|/,$n8)){ 
								if($n7=~m/(\S+)#(\S+)/){ ($n5,$n6)=($1,$2); if($n5=~m/^(.*):(.*)/){ if($2 ne 'neq'){ $n5=$1 } }; $n7="$n5#$n6" }else{ $n5=$n7; $n6=$n7 };
								$x=&uncodeXL($n6); if($x ne $n5){ $x="$n5#$x" }; 
								$layflow_v->{"$n7"}="Lime||||||$x|||$x"; 
								#&dbg(Dumper("Srvc:",$layflow_v->{$n7}),"to link:$n1\n"); 
								foreach $d (split(/\|/,$n1)){																# Link Dst-Srvc
									$obj={}; %{$obj}=('node1'=>$d,'node2'=>$n7,'color'=>'Lime'); push @layflow_e,$obj;
									#&dbg(Dumper("layflow_e",$obj)) 
						}	}	}
						else{ $n8=$n1 }
						if(defined $n2){ 																					# Source IP
							foreach $c (split(/\|/,$n2)){
								$x=&uncodeXL($c); $c="src$c"; 
								$layflow_v->{"$c"}="Aqua||||||$x|||$x"; 
								#&dbg(Dumper("Src:",$layflow_v->{$c}),"to link:$n8\n"); 
								foreach $n7 (split(/\|/,$n8)){	 															# Link Src-Srvc
									$obj={}; %{$obj}=('node1'=>$n7,'node2'=>$c,'color'=>'Aqua'); push @layflow_e,$obj; 
									#&dbg(Dumper("layflow_e",$obj))
		}	}	}	}	}	}	}	
		@match=(); $obj2="0a000000-0affffff"; push @match,$obj2; $obj2="ac100000-ac1fffff"; push @match,$obj2; $obj2="c0a80000-c0a8ffff"; push @match,$obj2;
		#&dbg(Dumper("Layout net object:\n",$net));
		foreach $x (sort keys %{$net}){																						# Add free private net segments to netList
			($n1,$n2)=$x=~m/^([0-9a-f]{8})-([0-9a-f]{8})\|.*$/; ($n5,$n6)=(hex($n1),hex($n2)); $n=scalar @match;
			while ($n--){
				$obj2=shift @match; ($n3,$n4)=$obj2=~m/^([0-9a-f]{8})-([0-9a-f]{8})$/; ($n7,$n8)=(hex($n3),hex($n4));
				if(($n6<=$n7||$n5>=$n8)){ push @match,$obj2 }
				elsif($n5>$n7&&$n6<$n8){ $obj2="$n3-".sprintf("%08x",$n5-1); push @match,$obj2; $obj2=sprintf("%08x",$n6+1)."-$n4"; push @match,$obj2 }
				elsif($n5<=$n7&&$n6<$n8){ $obj2=sprintf("%08x",$n6+1)."-$n4"; push @match,$obj2 }
				elsif($n5>$n7&&$n6>=$n8){ $obj2="$n3-".sprintf("%08x",$n5-1); push @match,$obj2 }
				#&dbg(Dumper($x,@match))
		}	}
		$n=scalar @match; $flag=''; 
		if($n>0){ 
			while($n--){ 
				$c=shift @match; $x=&uncodeXL($c); $net->{"$c|$x"}="*Free Private*"; $laynet_v->{"$c"}="Lime|||||Skip|$x|||$x *Free Private*";
				if($c eq "0a000000-0affffff"){ $flag .= "0a " }elsif($c eq "ac100000-ac1fffff"){ $flag .= "ac " }elsif($c eq "c0a80000-c0a8ffff"){ $flag .= "c0 " }
				#&dbg("laynet_v->{$c} line ".__LINE__."\n") 
		}	}
		if($LayNetwork){																									# Network Layout processing
			foreach $n (sort keys %{$laynet_v}){ 																			# Link each network with smallest network found
				($n1,$n2)=$n=~m/^(\S+)-(\S+)$/; @match=();
				$laynet_v->{"00000000-ffffffff"}="Red||||||AnyIP|||All Networks";
				$laynet_v->{"0a000000-0affffff"}="White||||||10.0.0.0-10.255.255.255|||Private 10/8 Net" unless $flag=~m/0a/;
				$laynet_v->{"ac100000-ac1fffff"}="White||||||172.16.0.0-172.31.255.255|||Private 172.16/12 Net" unless $flag=~m/ac/;
				$laynet_v->{"c0a80000-c0a8ffff"}="White||||||192.168.0.0-192.168.255.255|||Private 192.168/16 Net" unless $flag=~m/c0/;
				foreach $x (sort keys %{$laynet_v}){
					($n3,$n4)=$x=~m/^(\S+)-(\S+)$/;
					if(((hex($n1)>=hex($n3))&&(hex($n2)<hex($n4)))||(hex($n1)>hex($n3))&&(hex($n2)<=hex($n4))){				# $n is inside $x
						$count=scalar @match;
						if($count){ 
							while($count--){ 
								$z=shift @match; ($n5,$n6)=$z=~m/^(\S+)-(\S+)$/; 
								if(((hex($n3)>=hex($n5))&&(hex($n4)<hex($n6)))||(hex($n3)>hex($n5))&&(hex($n4)<=hex($n6))){ push @match,$x }else{ push @match,$z }	# replace if $x inside $z
						}	}
						else{ push @match,$x }																				# Push parent network ($x)
				}	}
				$count=scalar @match; while($count--){ $z=shift @match; $obj={}; %{$obj}=('node1'=>$n,'node2'=>$z); push @laynet_e,$obj; 
				#&dbg(Dumper("laynet_e",$obj)) 
			}	}
			if(scalar @laynet_e){
				$obj={}; %{$obj}=('node1'=>"0a000000-0affffff",'node2'=>"00000000-ffffffff"); push @laynet_e,$obj; 
				#&dbg(Dumper("laynet_e",$obj));
				$obj={}; %{$obj}=('node1'=>"ac100000-ac1fffff",'node2'=>"00000000-ffffffff"); push @laynet_e,$obj; 
				#&dbg(Dumper("laynet_e",$obj));
				$obj={}; %{$obj}=('node1'=>"c0a80000-c0a8ffff",'node2'=>"00000000-ffffffff"); push @laynet_e,$obj; 
				#&dbg(Dumper("laynet_e",$obj)); 
				@laynet_e=&unique(@laynet_e) }
			else{ $laynet_v={} }
		}
		if($LayPhysical){																									# Physical Layout processing
			$more=''; 
			#&dbg("Layout auxiliary PhyLayout objects\n".Dumper($mac,$switch,$map));
			foreach $p (keys %{$map}){																						# Create switch to switch connection
				($n1,$n2)=$map->{$p}=~m/^(\S+):(\S+)$/;																		# $n1 is MAC of remote switch and $n2 the remote interface
				if(exists($$mac{$n1})){
					if($mac->{$n1}=~m/([^~;]+):\S+;.*$/){ 
						$n1=$1; $n2=$switch->{"$n1:$n2"};
						if($n2&&($p ne "$n1:$n2")){ 
							$obj={}; %{$obj}=('node1'=>"$p",'node2'=>"$n1:$n2",'color'=>'White'); push @layphy_e,$obj; 		# connect local to remote switch
							#&dbg(Dumper("layphy_e",$obj));	
							$more .= "$p;$n1:$n2;"; $layphy_v->{"$p"}=~s/Skip//; $layphy_v->{"$n1:$n2"}=~s/Skip//;			# show switch-to-switch interfaces
			}	}	}	}
			#&dbg("Layout auxiliary PhyLayout objects (sw-sw ifs)\n".Dumper($more));
			foreach $p (keys %{$mac}){																						# mac to switch port connection
				if($p=~m/^([a-f\d]{2})[a-f\d]{2}\.[a-f\d]{4}\.[a-f\d]{4}$/){												# only for case 'mac -> host:if1;host:if2;...'
					$n2=$1; @match=split(/;/,$mac->{$p}); 																	# first MAC byte will be checked (unicast,globally administered)
					foreach $n (@match){ 
						if($more!~m/$n;/){																					# MAC is not in a Sw-Sw connection
							if(($n=~m/^~vlan~:(\d+)$/i)||($n=~/\S+\.(\d+)/i)){ 
								$obj={}; %{$obj}=('node1'=>$p,'node2'=>"~vlan~:$1",'color'=>'Aqua'); push @layphy_e,$obj; 	# MAC to Vlan link
								#&dbg(Dumper("layphy_e",$obj)) 
							}	
							elsif($n=~m/^(\S+):self$/){ 
								$obj={}; %{$obj}=('node1'=>$p,'node2'=>$1,'color'=>'Aqua'); push @layphy_e,$obj; 			# MAC to host link
								#&dbg(Dumper("layphy_e",$obj)) 
							}	
							if($n!~m/^(~vlan~:\d+)|(\S+:self)$/){
								$obj={}; %{$obj}=('node1'=>$n,'node2'=>$p,'color'=>'Aqua'); push @layphy_e,$obj; 			# MAC to interface link
								#&dbg(Dumper("layphy_e",$obj)); 
								$n1=$n;	
								if((!(3&hex($n2)))&&($p!~m/(0000\.0c07\.ac|0000\.0c9f\.f|0000\.5e00\.01|0007\.b4|0000\.0000\.fe|0098.7654.3210)/)){	# check MAC type (unicast+universal+not HSRP/VRRP/GLBP/CPHA/F5-TMM)
									foreach $n (@match){ 																	# direct interface to interface link when seen same MAC
										if(($n1 ne $n)&&($n!~m/^(~vlan~:\d+)|(vl\d+)|(\S+:self)$/i)&&($more!~m/$n;/)){
											if(exists($$layphy_v{"$n"})&&$layphy_v->{"$n"}=~m/(Yellow|White|Lime)/){ $layphy_v->{"$n"}=~s/Skip// } 
											if(exists($$layphy_v{"$n1"})&&$layphy_v->{"$n1"}=~m/(Yellow|White|Lime)/){ $layphy_v->{"$n1"}=~s/Skip// }; 
											if($n=~/\S+\.(\d+)/){ $layphy_v->{"~vlan~:$1"}=~s/Skip// }elsif($n1=~/\S+\.(\d+)/){ $layphy_v->{"~vlan~:$1"}=~s/Skip// }
											else{ 
												$obj={}; %{$obj}=('node1'=>$n1,'node2'=>$n,'color'=>'Olive'); push @layphy_e,$obj; 
												#&dbg(Dumper("layphy_e",$obj))
			}	}	}	}	}	}	}	}	}
			@layphy_e=&unique(@layphy_e) }
		if($LayLogical){																									# Logical Layout processing
			$map=""; 
			#&dbg("Layout auxiliary LogLayout object\n".Dumper($hash));
			foreach $p (keys %{$hash}){
				if($p=~m/(\S+):(\S+):connected/){																			# Layout directly connected networks
					$x=$hash->{$p}; $host=$1; $n=$2; if($n!~m/^.*\/32$/){ $c='Yellow' }else{ $c='Aqua' }; $flag='not-matched'; $count=scalar(@laylog_e); # $x is xlcode and $n is dotted decimal of a net
					while($count--){
						$obj2=$laylog_e[$count]; 
						if(($host eq $obj2->{'node1'})&&($obj2->{'node2'} ne $x)&&(&xlmatch($obj2->{'node2'},$x,'layout'))){			# if IP inside network, create and connect network node
							if(!exists($$laylog_v{$x})){ 
								$laylog_v->{$x}="$c||||||$n|||$n"; 
								#&dbg("laylog_v->{$x} line ".__LINE__."\n") 
							}; 
							$obj={}; %{$obj}=('node1'=>$obj2->{'node2'},'node2'=>$x,'color'=>'Aqua'); push @laylog_e,$obj; 
							#&dbg(Dumper("laylog_e",$obj));
							if(index($map,$x)==-1){ $map .= $x }; $flag='match';						# create map of connected networks
					}	}
					if($flag eq 'not-matched'){ 																			# associante with host if no IP to associate (ex. localhost networks)
						if(!exists($$laylog_v{$x})){ 
							$laylog_v->{$x}="$c|||||Skip|$n|||$n"; 
							#&dbg("laylog_v->{$x} line ".__LINE__."\n") 
						} 
						$obj={}; %{$obj}=('node1'=>$host,'node2'=>$x,'color'=>'Yellow'); push @laylog_e,$obj;
						#&dbg("laylog_v->{$x} line ".__LINE__."\n"); &dbg(Dumper("laylog_e",$obj));
			}	}	}
			foreach $p (keys %{$hash}){
				($host,$n,$ip)=split(/:/,$p); 																				# Format: 'origin:network:gw -> net xlcode'
				if($ip ne 'connected'){																						# Layout networks and gateways not yet drawn
					($x,$z)=split(/:/,$hash->{$p});
					if(!exists($$laylog_v{$z})){																			# create non-existent gateway node
						$laylog_v->{$z}="Orange|||||Skip|$ip|||$ip"; 
						#&dbg("laylog_v->{$z} line ".__LINE__."\n");
						foreach $c (keys %{$laylog_v}){																		# find local network node to connect gateway
							if(($z ne $c)&&($c=~m/^([0-9a-f]{8})-([0-9a-f]{8})$/)&&(&xlmatch($z,$c))){ $obj={}; %{$obj}=('node1'=>$z,'node2'=>$c,'color'=>'Orange'); push @laylog_e,$obj }
					}	}
					if(index($map,$x)==-1){ 																				# if remote network not yet created
						if($x eq '00000000-ffffffff'){ $c='Red' }else{ $c='White' }; $laylog_v->{$x}="$c|||||Skip|$n|||$n"; 
						#&dbg("laylog_v->{$x} line ".__LINE__."\n")
					}
					$obj={}; %{$obj}=('node1'=>$z,'node2'=>$x,'color'=>'Orange'); push @laylog_e,$obj; 						# connect remote network with gateway
					#&dbg(Dumper("laylog_e",$obj));	
			}	}
			@laylog_e=&unique(@laylog_e) }
		if($LayFlow&&scalar @layflow_e){ $layflow_v->{"00000000-ffffffff"}="Red||||||AnyIP|||All Networks"; $layflow_v->{"0000-0000"}="Red||||||AnyPort|||All Ports"; @layflow_e=&unique(@layflow_e) }
		#&dbg("Layout objects\n".Dumper($net,@layphy_e,$layphy_v,@laylog_e,$laylog_v,@laynet_e,$laynet_v,@layflow_e,$layflow_v))
		$now=time(); &dbg("#@# Finish Layout objects    \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n")
	}
	if($EXCEL){ &printXL($ExcelFile) }																						# Output to Excel file
	if($MYSQL){																												# Output to MySQL database
		use DBI;																											# Needed for export to MySQL
		use DBD::mysql;																										# Needed for export to MySQL
		$dbh=DBI->connect("DBI:mysql:database=mysql;host=$dbhost",$dbuser,$dbpass,{'RaiseError'=>1});
		$dbh->do("CREATE SCHEMA IF NOT EXISTS `$db` ;");																	# If doesn't exist create database and table
		foreach $n (sort keys %{$tb}){																						# Create and populate tables
			if($n eq 'X1-Main'){
				$dbh->do("CREATE TABLE IF NOT EXISTS `$db`.`$n` (
						  `id` 		INT NOT NULL AUTO_INCREMENT ,
						  `origin` 	VARCHAR( 255) NULL, `name`	VARCHAR(1000) NULL, `class`	 VARCHAR( 100) NULL, `member` VARCHAR( 255) NULL, `vlan` VARCHAR(5000) NULL,
						  `L1` 		VARCHAR(5000) NULL, `L2`	VARCHAR( 255) NULL, `L3`	 VARCHAR(1000) NULL, `L4`	  VARCHAR( 255) NULL,
						  `acl`		VARCHAR( 255) NULL, `vpn` 	VARCHAR( 255) NULL, `action` VARCHAR( 100) NULL, `status` VARCHAR( 255) NULL, `desc` VARCHAR(5000) NULL,
						  PRIMARY KEY (`id`) );"); }
			elsif($n eq 'X2-Xtend'){
				$dbh->do("CREATE TABLE IF NOT EXISTS `$db`.`$n` (
						  `id` 		INT NOT NULL AUTO_INCREMENT ,
						  `origin` 	VARCHAR( 255) NULL, `name` 	VARCHAR(1000) NULL, `class`	 VARCHAR( 100) NULL, `member` VARCHAR( 255) NULL, `vlan` VARCHAR(5000) NULL,
						  `L1` 		VARCHAR(5000) NULL, `L2`	VARCHAR( 255) NULL, `L3`	 VARCHAR(1000) NULL, `L4` 	  VARCHAR( 255) NULL,
						  `acl`		VARCHAR( 255) NULL, `vpn` 	VARCHAR( 255) NULL, `action` VARCHAR( 100) NULL, `status` VARCHAR( 255) NULL, `desc` VARCHAR(5000) NULL,
						  `XLcode` 	VARCHAR( 255) NULL,
						  PRIMARY KEY (`id`) );"); }
			elsif($n =~ /^(X3-Aux)|(X4-Net)$/){
				$dbh->do("CREATE TABLE IF NOT EXISTS `$db`.`$n` (
						  `id` 		INT NOT NULL AUTO_INCREMENT ,
						  `key`		VARCHAR(1000) NULL, `value` VARCHAR(20000) NULL,
						  PRIMARY KEY (`id`) );"); }
			elsif($n =~ /^X.*_e$/){
				$dbh->do("CREATE TABLE IF NOT EXISTS `$db`.`$n` (
						  `id` 		INT NOT NULL AUTO_INCREMENT ,
						  `node1`	VARCHAR( 100) NULL, `node2`			VARCHAR( 100) NULL, `color`	VARCHAR( 100) NULL, `width`		VARCHAR( 100) NULL, `style`		VARCHAR(100) NULL,
						  `opacity`	VARCHAR( 100) NULL, `visibility`	VARCHAR( 100) NULL, `label`	VARCHAR( 100) NULL, `l_color`	VARCHAR( 100) NULL, `l_size`	VARCHAR(100) NULL,
						  PRIMARY KEY (`id`) );"); }
			elsif($n =~ /^X.*_v$/){
				$dbh->do("CREATE TABLE IF NOT EXISTS `$db`.`$n` (
						  `id` 		INT NOT NULL AUTO_INCREMENT ,
						  `node`	VARCHAR( 100) NULL, `color`			VARCHAR( 100) NULL, `shape`	VARCHAR( 100) NULL, `size`		VARCHAR( 100) NULL, `opacity`		VARCHAR(100) NULL,
						  `i_file`	VARCHAR( 100) NULL, `visibility`	VARCHAR( 100) NULL, `label`	VARCHAR( 100) NULL, `l_color`	VARCHAR( 100) NULL, `l_position`	VARCHAR(100) NULL,
						  `tooltip`	VARCHAR( 100) NULL,
						  PRIMARY KEY (`id`) );"); }
			$dbh->do("TRUNCATE `$db`.`$n`;");																				# Clean and populate MySQL table
			if ( UNIVERSAL::isa($tb->{$n},'ARRAY') ) {
				foreach $obj (@{$tb->{$n}}){ $z=join("~,~",values($obj))=~s/'/\\'/rg; $z=~s/~,~/','/g; $dbh->do("INSERT IGNORE INTO `$db`.`$n` (`".join('`,`',keys($obj))."`) VALUES ('".$z."')") } }
			else{
				if($n =~ /^(X3-Aux)|(X4-Net)$/){ $obj=$tb->{$n}; foreach $x (keys %{$obj}){ $dbh->do("INSERT IGNORE INTO `$db`.`$n` (`key`,`value`) VALUES ('".$x."','".$obj->{$x}."')") } }
				elsif($n =~ /^X.*_v$/){ $obj=$tb->{$n}; foreach $x (keys %{$obj}){ $dbh->do("INSERT IGNORE INTO `$db`.`$n` VALUES (NULL,'".$x."','".join("','",split(/\|/,$obj->{$x}))."')") } }
				else{ $obj=$tb->{$n}; foreach $x (keys %{$obj}){ $dbh->do("INSERT IGNORE INTO `$db`.`$n` (`".join('`,`',keys(%{$obj}))."`) VALUES ('".join("','",values(%{$obj}))."')") }
		}	}	}
		$dbh->disconnect();
		$now=time(); &dbg("#@# Finish writing MySQL    \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n")
	}
}
# End function: execute

# Start function: search
sub search{																													# Lookup objects that include the L1|L2|L3|L4 input values
	use Spreadsheet::XLSX;
	use utf8;																												# For Excel file reading process
	use Text::Iconv;
	GetOptions ("i=s"=>\$ExcelFile,"o=s"=>\$SearchFile,"v"=>\$v);															# Get options: -v -i LanMngmtXL.xlsx -o LanMngmtXL-search.xlsx
	$now=time(); &dbg("#@# Opening xlsx            \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d\n", &memory_usage()));
	my $converter=Text::Iconv->new("utf-8","iso-8859-1");																	# conversion format when reading Excel file
	$workbook=Spreadsheet::XLSX->new($ExcelFile,$converter) or die "Couldn't open file $ExcelFile";
	$now=time(); &dbg("#@# Start reading xlsx      \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d\n", &memory_usage()));
	foreach $xls (@{$workbook->{Worksheet}}) {																				# Import Excel data
		if(($xls->{Name} eq 'X1-Main')||($xls->{Name} eq 'X2-Xtend')){
			foreach $row ($xls->{MinRow} .. $xls->{MaxRow}){
				$obj={}; foreach $x ($xls->{MinCol} ..  $xls->{MaxCol}){ $cell=$xls->{Cells}[$row][$x]; if($cell){ $obj->{$i->{$x}}=decode_entities($cell->{Val}) } }
				unless($row==0){ push $tb->{$xls->{Name}},$obj }
		}	}
		elsif($xls->{Name} eq 'X3-Aux'){ $aux={}; foreach $row ($xls->{MinRow} .. $xls->{MaxRow}){ $x=$xls->{Cells}[$row][0]; $cell=$xls->{Cells}[$row][1]; $aux->{$x->{Val}}=$cell->{Val} } }
	}
	$now=time(); &dbg("#@# End   reading xlsx      \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d\n", &memory_usage()));
	$map=lc(Dumper($aux));																									# Search is case-insensitive
	foreach $p (@ARGV){																										# Process search arguments
		$p=lc($p);
		if($p=~m/^([a-f\d]{4}\.[a-f\d]{4}\.[a-f\d]{4})$/){ $more .= "$p;" }													# L2(MAC) address object
		elsif($p=~m/^([a-f\d]{2}):([a-f\d]{2}):([a-f\d]{2}):([a-f\d]{2}):([a-f\d]{2}):([a-f\d]{2})$/){ $more .= "$1$2.$3$4.$5$6;" } 	# L2(MAC) alternate format
		else{
			$more .= lc("|$p");
			@match=$map=~m/\s+"\w:\S+:l\d:$p" => "(.*)"/g;																	# Lookup members or translated name
			if(scalar @match){ foreach $member (@match){ $more .= " $member" } }
			else{ @match=$map=~m/\s+"\w:\S+:l\d:(\S+)" => .*["\s]$p["\s]/g; if(scalar @match){ foreach $member (@match){ $more .= " $member" } } }	# Lookup membership or reverse name
			$more .= "|;";
	}	}
	&dbg("Lookup (phase 1): $more\n");
	$map=$more; $more=''; $count=0;																							# $count used for total number of detected L3/L4 objects
	foreach $p (split(/;/, $map)){
		if($p=~m/^\|(.*)\|$/){
			$n1=$1; $ext='';
			foreach $n (split(/\s+/, $n1)){
				$c=&xlcode($n); 																							# If exists XLcode of argument, add it
				if($c){ $ext .= " ~$c~"; $count++ }
				elsif($n=~m/(\S+)#(\S+)/){ $prot=$1; $srvc=$2; if(exists($$ServicePort::services{$srvc})){ $ext .= " $prot#".$ServicePort::services->{$srvc} } } }	# Translate port name
			$more .= "|".$n1.$ext."|;" }
		else{
			$c=&xlcode($p); $ext='';
			if($c){ $ext .= " ~$c~"; $count++; $more .= "|".$p.$ext."|;" }
			elsif($p=~m/(\S+)#(\S+)/){ $prot=$1; $srvc=$2; if(exists($$ServicePort::services{$srvc})){ $ext .= " $prot#".$ServicePort::services->{$srvc} }; $more .= "|".$p.$ext."|;" }
			else{ $more .= "$p;" }
	}	}
	$ext=$count;
	&dbg("Lookup (phase 2): $more\n");
	for $map (\@objects,\@xtend){
		$n=scalar @$map;
		while ($n--){																										# Lookup objects
			$obj2=shift $map; $c=join(' ', grep { $_ !~ m{desc =>} } split(/\n/, Dumper($obj2)) ); $flag='matched'; $status=0b000000000; $count=$ext;
			&dbg("\nChecking:$c");
			foreach $p (split(/;/, $more)){																					# search type: 'and' of each argument
				$srvc='';
				if($p=~m/^\|(.*)\|$/){																						# search type: 'or' of each alternative argument
					$action='not-matched'; $srvc2='';
					foreach $x (split(/\s+/, $1)){
						&dbg("\nLookupOR: $x ..."); if($x=~m/^(\S+)#\S+$/i){ $srvc=$1 };
						if($x=~m/~(.*)~/){
							$x=$1;
							if($c=~m/ XLcode => "(.*?)"/){
								if($v){ $r=&xlmatch($1,$x) }else{ $r=&xlmatch($x,$1) }; $srvc2='' unless ($srvc2)=(($x=~m/^[0-9a-f]{4}-[0-9a-f]{4}$/)&&($c=~m/N(\S+)L/));
								if($r&&($srvc2=~m/(^|[^\w-])$srvc([^\w-]|$)/)){ $status |= $r; $action='matched'; &dbg(sprintf("matched OR (r:%09b,s:%09b)",$r,$status)) }
						} 	}
						elsif($c=~m/(\"|\s)$x(\"|\s|\/\d{1,2})/i){ $count--; $action='matched'; &dbg("matched OR") } }
					if($action eq 'not-matched'){ $flag='not-matched'; &dbg("\nGlobally not matched OR") }else{ &dbg("\nGlobally matched OR") } }
				elsif($p=~m/^\?(.*)$/){
					$x=$1; &dbg("\nLookupAND: $x ..."); $c=join(' ', grep { $_=~m{desc =>} } split(/\n/, Dumper($obj2)) );
					if($c=~m/\W$x\W/i){ &dbg("matched AND") }else{ $flag='not-matched' } }
				else{ $x=$p; &dbg("\nLookupAND: $x ..."); if($c=~m/(\"|\s)$x(\"|\s)/i){ &dbg("matched AND"); }else{ $flag='not-matched' } } }
			$status =()= sprintf("%09b",$status)=~/1/g;
			if(($flag eq 'matched')&&($count<=$status)){ push $map,$obj2; &dbg("\nMatched and pushed\n") }else{ &dbg("\nNot matched or fail count($count)/status($status)\n") }
	}	}
	$now=time(); &dbg("#@# Finish search           \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
	&printXL($SearchFile);
}
# End function: search

# START
Getopt::Long::Configure("pass_through"); GetOptions ("a=s"=>\$n,"x=s"=>\$x); 												# Get options: -a get|exec|search
if($x=~m/^([0-1])[0-1]{13}$/){ $DEBUG=$1 }; if($DEBUG){ open(DBG, ">LanMngmtXL-debug.txt") or warn "Can't write: $!" };
$now=time(); &dbg("> Start                     \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
if($n=~m/get/){ &get }elsif($n=~m/exec/){ &execute }elsif($n=~m/search/){ &search }
#elsif($n=~m/test/){ print "Used for quick test: ".&xlcode($ARGV[0])."\n" }													# To test partial code execution
elsif($n=~m/test/){ printf("xlmatch %09b (search) %09b (covered) %09b (layout)\n",&xlmatch($ARGV[0],$ARGV[1]),&xlmatch($ARGV[0],$ARGV[1],'quick'),&xlmatch($ARGV[0],$ARGV[1],'layout')) }
else{ print "Usage perl LanMngmtXL -a <action=get|exec|search> <other options> <arguments>\n" }
$now=time(); &dbg("#@# Finish                   \@time:".sprintf("%8.2f", $now-$start)." \@mem:".sprintf("%10d", &memory_usage())."\n");
if($DEBUG){ close(DBG) }
POSIX::_exit(0)
# END
